<commit_before>(function(root) {

// Import
// ========

var _,
    errors,
    util,
    Chronicle,
    Compound,
    TextOperation,
    ArrayOperation;

if (typeof exports !== 'undefined') {
  _ = require('underscore');
  errors   = require('../util/errors');
  util   = require('../util/util');
  Chronicle = require('../../chronicle');
  Compound = require('./compound');
  TextOperation = require('./text_operation');
  ArrayOperation = require('./array_operation');
} else {
  _ = root._;
  errors = root.Substance.errors;
  util = root.Substance.util;
  Chronicle = root.Substance.Chronicle;
  Compound = Chronicle.ot.Compound;
  TextOperation = Chronicle.ot.TextOperation;
  ArrayOperation = Chronicle.ot.ArrayOperation;
}

var NOP = 0;
var CREATE = '+';
var DELETE = '-';
var UPDATE = '|';
var SET = '=';

var ObjectOperation = function(data) {

  this.type = data.type;
  this.path = data.path;

  if (this.type === CREATE || this.type === DELETE) {
    this.val = data.val;
  }

  // Updates can be given as value or as Operation (Text, Array)
  else if (this.type === UPDATE) {
    if (data.diff !== undefined) {
      this.diff = data.diff;
      this.propertyType = data.propertyType;
    } else {
      throw new errors.ChronicleError("Illegal argument: update by value or by diff must be provided");
    }
  }

  else if (this.type === SET) {
    this.val = data.val;
    this.original = data.original;
  }

};

ObjectOperation.fromJSON = function(data) {

  if (data.type === Compound.TYPE) {
    var ops = [];
    for (var idx = 0; idx < data.ops.length; idx++) {
      ops.push(ObjectOperation.fromJSON(data.ops[idx]));
    }
    return ObjectOperation.Compound(ops);

  } else {
    return new ObjectOperation(data);
  }
};

ObjectOperation.__prototype__ = function() {

  this.clone = function() {
    return new ObjectOperation(this);
  };

  this.isNOP = function() {
    return this.type === NOP;
  };

  this.apply = function(obj) {
    if (this.type === NOP) return obj;

    // Note: this allows to use a custom adapter implementation
    // to support other object like backends
    var adapter = (obj instanceof ObjectOperation.Object) ? obj : new ObjectOperation.Object(obj);
    var val = adapter.get(this.path);

    if (this.type === CREATE) {
      adapter.create(this.path, util.clone(this.val));
    }

    else if (this.type === DELETE) {
      // TODO: maybe we could tolerate such deletes
      if (val === undefined) {
        throw new errors.ChronicleError("Property " + JSON.stringify(this.path) + " not found.");
      }
      adapter.delete(this.path, val);
    }

    else if (this.type === UPDATE) {
      if (this.propertyType === 'string') {
        val = TextOperation.apply(this.diff, val);
        adapter.set(this.path, val);
      }
      else if (this.propertyType === 'array') {
        val = ArrayOperation.apply(this.diff, val);
        if(!adapter.inplace()) adapter.set(this.path, val);
      }
      else {
        throw new errors.ChronicleError("Unsupported type for operational update.");
      }
    }

    else if (this.type === SET) {
      adapter.set(this.path, this.val);
    }

    else {
      throw new errors.ChronicleError("Illegal state.");
    }

    return obj;
  };

  this.invert = function() {

    if (this.type === NOP) {
      return { type: NOP };
    }

    var result = new ObjectOperation(this);

    if (this.type === CREATE) {
      result.type = DELETE;
    }

    else if (this.type === DELETE) {
      result.type = CREATE;
    }

    else if (this.type === UPDATE) {
      var invertedDiff;
      if (this.propertyType === 'string') {
        invertedDiff = TextOperation.fromJSON(this.diff).invert();
      } else if (this.propertyType === 'array') {
        invertedDiff = ArrayOperation.fromJSON(this.diff).invert();
      }
      result.diff = invertedDiff;
      result.propertyType = this.propertyType;
    }

    else if (this.type === SET) {
      result.val = this.original;
      result.original = this.val;
    }

    else {
      throw new errors.ChronicleError("Illegal state.");
    }

    return result;
  };

  this.hasConflict = function(other) {
    return ObjectOperation.hasConflict(this, other);
  };

  this.toJSON = function() {

    if (this.type === NOP) {
      return {
        type: NOP
      };
    }

    var data = {
      type: this.type,
      path: this.path,
    };

    if (this.type === CREATE || this.type === DELETE) {
      data.val = this.val;
    }

    else if (this.type === UPDATE) {
      data.diff = this.diff;
      data.propertyType = this.propertyType;
    }

    else if (this.type === SET) {
      data.val = this.val;
      data.original = this.original;
    }

    return data;
  };

};
ObjectOperation.prototype = new ObjectOperation.__prototype__();

ObjectOperation.Object = function(obj) {
  this.obj = obj;
};

ObjectOperation.Object.__prototype__ = function() {

  function resolve(obj, path, create) {
    var item = obj;
    var idx = 0;
    for (; idx < path.length-1; idx++) {
      if (item === undefined) {
        throw new Error("Key error: could not find element for path " + JSON.stringify(this.path));
      }

      if (item[path[idx]] === undefined && create) {
        item[path[idx]] = {};
      }

      item = item[path[idx]];
    }
    return {parent: item, key: path[idx]};
  }

  this.get = function(path) {
    var item = resolve(this.obj, path);
    return item.parent[item.key];
  };

  this.create = function(path, value) {
    var item = resolve(this.obj, path, true);
    if (item.parent[item.key] !== undefined) {
      throw new errors.ChronicleError("Value already exists. path =" + JSON.stringify(path));
    }
    item.parent[item.key] = value;
  };

  this.set = function(path, value) {
    var item = resolve(this.obj, path);
    item.parent[item.key] = value;
  };

  this.delete = function(path) {
    var item = resolve(this.obj, path);
    delete item.parent[item.key];
  };

  this.inplace = function() {
    return true;
  };

};
ObjectOperation.Object.prototype = new ObjectOperation.Object.__prototype__();

var hasConflict = function(a, b) {
  if (a.type === NOP || b.type === NOP) return false;

  return _.isEqual(a.path, b.path);
};

var transform_delete_delete = function(a, b) {
  // both operations have the same effect.
  // the transformed operations are turned into NOPs
  a.type = NOP;
  b.type = NOP;
};

var transform_create_create = function() {
  // TODO: maybe it would be possible to create an differntial update that transforms the one into the other
  // However, we fail for now.
  throw new errors.ChronicleError("Can not transform two concurring creates of the same property");
};

var transform_delete_create = function(a, b, flipped) {
  if (a.type !== DELETE) {
    return transform_delete_create(b, a, true);
  }

  if (!flipped) {
    a.type = NOP;
  } else {
    a.val = b.val;
    b.type = NOP;
  }
};

var transform_delete_update = function(a, b, flipped) {
  if (a.type !== DELETE) {
    return transform_delete_update(b, a, true);
  }

  var op;
  if (b.propertyType === 'string') {
    op = TextOperation.fromJSON(b.diff);
  } else if (b.propertyType === 'array') {
    op = ArrayOperation.fromJSON(b.diff);
  }

  // (DELETE, UPDATE) is transformed into (DELETE, CREATE)
  if (!flipped) {
    a.type = NOP;
    b.type = CREATE;
    b.val = op.apply(a.val);
  }
  // (UPDATE, DELETE): the delete is updated to delete the updated value
  else {
    a.val = op.apply(a.val);
    b.type = NOP;
  }

};

var transform_create_update = function() {
  // it is not possible to reasonably transform this.
  throw new errors.ChronicleError("Can not transform a concurring create and update of the same property");
};

var transform_update_update = function(a, b) {

  // Note: this is a conflict the user should know about

  var op_a, op_b, t;
  if (b.propertyType === 'string') {
    op_a = TextOperation.fromJSON(a.diff);
    op_b = TextOperation.fromJSON(b.diff);
    t = TextOperation.transform(op_a, op_b, {inplace: true});
  } else if (b.propertyType === 'array') {
    op_a = ArrayOperation.fromJSON(a.diff);
    op_b = ArrayOperation.fromJSON(b.diff);
    t = ArrayOperation.transform(op_a, op_b, {inplace: true});
  }

  a.diff = t[0];
  b.diff = t[1];
};

var transform_create_set = function(a, b, flipped) {
  if (a.type !== CREATE) return transform_create_set(b, a, true);

  if (!flipped) {
    a.type = NOP;
    b.original = a.val;
  } else {
    a.type = SET;
    a.original = b.val;
    b.type = NOP;
  }

};

var transform_delete_set = function(a, b, flipped) {
  if (a.type !== DELETE) return transform_delete_set(b, a, true);

  if (!flipped) {
    a.type = NOP;
    b.type = CREATE;
    b.original = undefined;
  } else {
    a.val = b.val;
    b.type = NOP;
  }

};

var transform_update_set = function() {
  throw new errors.ChronicleError("Can not transform update/set of the same property.");
};

var transform_set_set = function(a, b) {
  a.type = NOP;
  b.original = a.val;
};

var _NOP = 0;
var _CREATE = 1;
var _DELETE = 2;
var _UPDATE = 4;
var _SET = 8;

var CODE = {
  0: _NOP,
  '+': _CREATE,
  '-': _DELETE,
  '|': _UPDATE,
  '=': _SET,
};

var __transform__ = [];
__transform__[_DELETE | _DELETE] = transform_delete_delete;
__transform__[_DELETE | _CREATE] = transform_delete_create;
__transform__[_DELETE | _UPDATE] = transform_delete_update;
__transform__[_CREATE | _CREATE] = transform_create_create;
__transform__[_CREATE | _UPDATE] = transform_create_update;
__transform__[_UPDATE | _UPDATE] = transform_update_update;
__transform__[_CREATE | _SET   ] = transform_create_set;
__transform__[_DELETE | _SET   ] = transform_delete_set;
__transform__[_UPDATE | _SET   ] = transform_update_set;
__transform__[_SET    | _SET   ] = transform_set_set;

var transform = function(a, b, options) {

  options = options || {};

  var conflict = hasConflict(a, b);

  if (options.check && conflict) {
    throw Chronicle.mergeConflict(a, b);
  }

  if (!options.inplace) {
    a = util.clone(a);
    b = util.clone(b);
  }

  // without conflict: a' = a, b' = b
  if (!conflict) {
    return [a, b];
  }

  __transform__[CODE[a.type] | CODE[b.type]](a,b);

  return [a, b];
};

ObjectOperation.transform = Compound.createTransform(transform);
ObjectOperation.hasConflict = hasConflict;

var __apply__ = function(op, obj) {
  if (!(op instanceof ObjectOperation)) {
    op = ObjectOperation.fromJSON(op);
  }
  return op.apply(obj);
};

// TODO: rename to "exec" or perform
ObjectOperation.apply = __apply__;

ObjectOperation.Create = function(path, val) {
  return new ObjectOperation({type: CREATE, path: path, val: val});
};

ObjectOperation.Delete = function(path, val) {
  return new ObjectOperation({type: DELETE, path: path, val: val});
};

function getPropertyType(op) {

  if (op instanceof Compound) {
    return getPropertyType(op.ops[0]);
  }
  if (op instanceof TextOperation) {
    return "string";
  }
  else if (op instanceof ArrayOperation) {
    return  "array";
  }
  else {
    throw new errors.ChronicleError("Unsupported type for incremental updates");
  }
}

ObjectOperation.Update = function(path, diff) {
  var propertyType = getPropertyType(diff);
  return new ObjectOperation({
    type: UPDATE,
    path: path,
    diff: diff,
    propertyType: propertyType
  });
};

ObjectOperation.Set = function(path, oldVal, newVal) {
  return new ObjectOperation({
    type: SET,
    path: path,
    val: newVal,
    original: oldVal
  });
};

ObjectOperation.Compound = function(ops) {
  return new Compound(ops);
};

// TODO: this can not deal with cyclic references
var __extend__ = function(obj, newVals, path, deletes, creates, updates) {
  var keys = Object.getOwnPropertyNames(newVals);

  for (var idx = 0; idx < keys.length; idx++) {
    var key = keys[idx];
    var p = path.concat(key);

    if (newVals[key] === undefined && obj[key] !== undefined) {
      deletes.push(ObjectOperation.Delete(p, obj[key]));

    } else if (_.isObject(newVals[key])) {

      // TODO: for now, the structure must be the same
      if (!_.isObject(obj[key])) {
        throw new errors.ChronicleError("Incompatible arguments: newVals must have same structure as obj.");
      }
      __extend__(obj[key], newVals[key], p, deletes, creates, updates);

    } else {
      if (obj[key] === undefined) {
        creates.push(ObjectOperation.Create(p, newVals[key]));
      } else {
        var oldVal = obj[key];
        var newVal = newVals[key];
        if (!_.isEqual(oldVal, newVal)) {
          updates.push(ObjectOperation.Set(p, oldVal, newVal));
        }
      }
    }
  }
};

ObjectOperation.Extend = function(obj, newVals) {
  var deletes = [];
  var creates = [];
  var updates = [];
  __extend__(obj, newVals, [], deletes, creates, updates);
  return ObjectOperation.Compound(deletes.concat(creates).concat(updates));
};

// Export
// ========

if (typeof exports !== 'undefined') {
  module.exports = ObjectOperation;
} else {
  Chronicle.ot = Chronicle.ot || {};
  Chronicle.ot.ObjectOperation = ObjectOperation;
}

})(this);
<commit_msg>ObjOperations allow ObjOperations in update.<commit_after>(function(root) {

// Import
// ========

var _,
    errors,
    util,
    Chronicle,
    Compound,
    TextOperation,
    ArrayOperation;

if (typeof exports !== 'undefined') {
  _ = require('underscore');
  errors   = require('../util/errors');
  util   = require('../util/util');
  Chronicle = require('../../chronicle');
  Compound = require('./compound');
  TextOperation = require('./text_operation');
  ArrayOperation = require('./array_operation');
} else {
  _ = root._;
  errors = root.Substance.errors;
  util = root.Substance.util;
  Chronicle = root.Substance.Chronicle;
  Compound = Chronicle.ot.Compound;
  TextOperation = Chronicle.ot.TextOperation;
  ArrayOperation = Chronicle.ot.ArrayOperation;
}

var NOP = 0;
var CREATE = '+';
var DELETE = '-';
var UPDATE = '|';
var SET = '=';

var ObjectOperation = function(data) {

  this.type = data.type;
  this.path = data.path;

  if (this.type === CREATE || this.type === DELETE) {
    this.val = data.val;
  }

  // Updates can be given as value or as Operation (Text, Array)
  else if (this.type === UPDATE) {
    if (data.diff !== undefined) {
      this.diff = data.diff;
      this.propertyType = data.propertyType;
    } else {
      throw new errors.ChronicleError("Illegal argument: update by value or by diff must be provided");
    }
  }

  else if (this.type === SET) {
    this.val = data.val;
    this.original = data.original;
  }

};

ObjectOperation.fromJSON = function(data) {

  if (data.type === Compound.TYPE) {
    var ops = [];
    for (var idx = 0; idx < data.ops.length; idx++) {
      ops.push(ObjectOperation.fromJSON(data.ops[idx]));
    }
    return ObjectOperation.Compound(ops);

  } else {
    return new ObjectOperation(data);
  }
};

ObjectOperation.__prototype__ = function() {

  this.clone = function() {
    return new ObjectOperation(this);
  };

  this.isNOP = function() {
    return this.type === NOP;
  };

  this.apply = function(obj) {
    if (this.type === NOP) return obj;

    // Note: this allows to use a custom adapter implementation
    // to support other object like backends
    var adapter = (obj instanceof ObjectOperation.Object) ? obj : new ObjectOperation.Object(obj);
    var val = adapter.get(this.path);

    if (this.type === CREATE) {
      adapter.create(this.path, util.clone(this.val));
    }

    else if (this.type === DELETE) {
      // TODO: maybe we could tolerate such deletes
      if (val === undefined) {
        throw new errors.ChronicleError("Property " + JSON.stringify(this.path) + " not found.");
      }
      adapter.delete(this.path, val);
    }

    else if (this.type === UPDATE) {
      if (this.propertyType === 'object') {
        val = ObjectOperation.apply(this.diff, val);
        if(!adapter.inplace()) adapter.set(this.path, val);
      }
      else if (this.propertyType === 'array') {
        val = ArrayOperation.apply(this.diff, val);
        if(!adapter.inplace()) adapter.set(this.path, val);
      }
      else if (this.propertyType === 'string') {
        val = TextOperation.apply(this.diff, val);
        adapter.set(this.path, val);
      }
      else {
        throw new errors.ChronicleError("Unsupported type for operational update.");
      }
    }

    else if (this.type === SET) {
      adapter.set(this.path, this.val);
    }

    else {
      throw new errors.ChronicleError("Illegal state.");
    }

    return obj;
  };

  this.invert = function() {

    if (this.type === NOP) {
      return { type: NOP };
    }

    var result = new ObjectOperation(this);

    if (this.type === CREATE) {
      result.type = DELETE;
    }

    else if (this.type === DELETE) {
      result.type = CREATE;
    }

    else if (this.type === UPDATE) {
      var invertedDiff;
      if (this.propertyType === 'string') {
        invertedDiff = TextOperation.fromJSON(this.diff).invert();
      } else if (this.propertyType === 'array') {
        invertedDiff = ArrayOperation.fromJSON(this.diff).invert();
      }
      result.diff = invertedDiff;
      result.propertyType = this.propertyType;
    }

    else if (this.type === SET) {
      result.val = this.original;
      result.original = this.val;
    }

    else {
      throw new errors.ChronicleError("Illegal state.");
    }

    return result;
  };

  this.hasConflict = function(other) {
    return ObjectOperation.hasConflict(this, other);
  };

  this.toJSON = function() {

    if (this.type === NOP) {
      return {
        type: NOP
      };
    }

    var data = {
      type: this.type,
      path: this.path,
    };

    if (this.type === CREATE || this.type === DELETE) {
      data.val = this.val;
    }

    else if (this.type === UPDATE) {
      data.diff = this.diff;
      data.propertyType = this.propertyType;
    }

    else if (this.type === SET) {
      data.val = this.val;
      data.original = this.original;
    }

    return data;
  };

};
ObjectOperation.prototype = new ObjectOperation.__prototype__();

ObjectOperation.Object = function(obj) {
  this.obj = obj;
};

ObjectOperation.Object.__prototype__ = function() {

  function resolve(obj, path, create) {
    var item = obj;
    var idx = 0;
    for (; idx < path.length-1; idx++) {
      if (item === undefined) {
        throw new Error("Key error: could not find element for path " + JSON.stringify(this.path));
      }

      if (item[path[idx]] === undefined && create) {
        item[path[idx]] = {};
      }

      item = item[path[idx]];
    }
    return {parent: item, key: path[idx]};
  }

  this.get = function(path) {
    var item = resolve(this.obj, path);
    return item.parent[item.key];
  };

  this.create = function(path, value) {
    var item = resolve(this.obj, path, true);
    if (item.parent[item.key] !== undefined) {
      throw new errors.ChronicleError("Value already exists. path =" + JSON.stringify(path));
    }
    item.parent[item.key] = value;
  };

  this.set = function(path, value) {
    var item = resolve(this.obj, path);
    item.parent[item.key] = value;
  };

  this.delete = function(path) {
    var item = resolve(this.obj, path);
    delete item.parent[item.key];
  };

  this.inplace = function() {
    return true;
  };

};
ObjectOperation.Object.prototype = new ObjectOperation.Object.__prototype__();

var hasConflict = function(a, b) {
  if (a.type === NOP || b.type === NOP) return false;

  return _.isEqual(a.path, b.path);
};

var transform_delete_delete = function(a, b) {
  // both operations have the same effect.
  // the transformed operations are turned into NOPs
  a.type = NOP;
  b.type = NOP;
};

var transform_create_create = function() {
  // TODO: maybe it would be possible to create an differntial update that transforms the one into the other
  // However, we fail for now.
  throw new errors.ChronicleError("Can not transform two concurring creates of the same property");
};

var transform_delete_create = function(a, b, flipped) {
  if (a.type !== DELETE) {
    return transform_delete_create(b, a, true);
  }

  if (!flipped) {
    a.type = NOP;
  } else {
    a.val = b.val;
    b.type = NOP;
  }
};

var transform_delete_update = function(a, b, flipped) {
  if (a.type !== DELETE) {
    return transform_delete_update(b, a, true);
  }

  var op;
  if (b.propertyType === 'string') {
    op = TextOperation.fromJSON(b.diff);
  } else if (b.propertyType === 'array') {
    op = ArrayOperation.fromJSON(b.diff);
  }

  // (DELETE, UPDATE) is transformed into (DELETE, CREATE)
  if (!flipped) {
    a.type = NOP;
    b.type = CREATE;
    b.val = op.apply(a.val);
  }
  // (UPDATE, DELETE): the delete is updated to delete the updated value
  else {
    a.val = op.apply(a.val);
    b.type = NOP;
  }

};

var transform_create_update = function() {
  // it is not possible to reasonably transform this.
  throw new errors.ChronicleError("Can not transform a concurring create and update of the same property");
};

var transform_update_update = function(a, b) {

  // Note: this is a conflict the user should know about

  var op_a, op_b, t;
  if (b.propertyType === 'string') {
    op_a = TextOperation.fromJSON(a.diff);
    op_b = TextOperation.fromJSON(b.diff);
    t = TextOperation.transform(op_a, op_b, {inplace: true});
  } else if (b.propertyType === 'array') {
    op_a = ArrayOperation.fromJSON(a.diff);
    op_b = ArrayOperation.fromJSON(b.diff);
    t = ArrayOperation.transform(op_a, op_b, {inplace: true});
  } else if (b.propertyType === 'object') {
    op_a = ObjectOperation.fromJSON(a.diff);
    op_b = ObjectOperation.fromJSON(b.diff);
    t = ObjectOperation.transform(op_a, op_b, {inplace: true});
  }

  a.diff = t[0];
  b.diff = t[1];
};

var transform_create_set = function(a, b, flipped) {
  if (a.type !== CREATE) return transform_create_set(b, a, true);

  if (!flipped) {
    a.type = NOP;
    b.original = a.val;
  } else {
    a.type = SET;
    a.original = b.val;
    b.type = NOP;
  }

};

var transform_delete_set = function(a, b, flipped) {
  if (a.type !== DELETE) return transform_delete_set(b, a, true);

  if (!flipped) {
    a.type = NOP;
    b.type = CREATE;
    b.original = undefined;
  } else {
    a.val = b.val;
    b.type = NOP;
  }

};

var transform_update_set = function() {
  throw new errors.ChronicleError("Can not transform update/set of the same property.");
};

var transform_set_set = function(a, b) {
  a.type = NOP;
  b.original = a.val;
};

var _NOP = 0;
var _CREATE = 1;
var _DELETE = 2;
var _UPDATE = 4;
var _SET = 8;

var CODE = {
  0: _NOP,
  '+': _CREATE,
  '-': _DELETE,
  '|': _UPDATE,
  '=': _SET,
};

var __transform__ = [];
__transform__[_DELETE | _DELETE] = transform_delete_delete;
__transform__[_DELETE | _CREATE] = transform_delete_create;
__transform__[_DELETE | _UPDATE] = transform_delete_update;
__transform__[_CREATE | _CREATE] = transform_create_create;
__transform__[_CREATE | _UPDATE] = transform_create_update;
__transform__[_UPDATE | _UPDATE] = transform_update_update;
__transform__[_CREATE | _SET   ] = transform_create_set;
__transform__[_DELETE | _SET   ] = transform_delete_set;
__transform__[_UPDATE | _SET   ] = transform_update_set;
__transform__[_SET    | _SET   ] = transform_set_set;

var transform = function(a, b, options) {

  options = options || {};

  var conflict = hasConflict(a, b);

  if (options.check && conflict) {
    throw Chronicle.mergeConflict(a, b);
  }

  if (!options.inplace) {
    a = util.clone(a);
    b = util.clone(b);
  }

  // without conflict: a' = a, b' = b
  if (!conflict) {
    return [a, b];
  }

  __transform__[CODE[a.type] | CODE[b.type]](a,b);

  return [a, b];
};

ObjectOperation.transform = Compound.createTransform(transform);
ObjectOperation.hasConflict = hasConflict;

var __apply__ = function(op, obj) {
  if (!(op instanceof ObjectOperation)) {
    op = ObjectOperation.fromJSON(op);
  }
  return op.apply(obj);
};

// TODO: rename to "exec" or perform
ObjectOperation.apply = __apply__;

ObjectOperation.Create = function(path, val) {
  return new ObjectOperation({type: CREATE, path: path, val: val});
};

ObjectOperation.Delete = function(path, val) {
  return new ObjectOperation({type: DELETE, path: path, val: val});
};

function guessPropertyType(op) {

  if (op instanceof Compound) {
    return guessPropertyType(op.ops[0]);
  }
  if (op instanceof TextOperation) {
    return "string";
  }
  else if (op instanceof ArrayOperation) {
    return  "array";
  }
  else {
    return "other";
  }
}

ObjectOperation.Update = function(path, diff, propertyType) {
  propertyType = propertyType || guessPropertyType(diff);

  return new ObjectOperation({
    type: UPDATE,
    path: path,
    diff: diff,
    propertyType: propertyType
  });
};

ObjectOperation.Set = function(path, oldVal, newVal) {
  return new ObjectOperation({
    type: SET,
    path: path,
    val: newVal,
    original: oldVal
  });
};

ObjectOperation.Compound = function(ops) {
  return new Compound(ops);
};

// TODO: this can not deal with cyclic references
var __extend__ = function(obj, newVals, path, deletes, creates, updates) {
  var keys = Object.getOwnPropertyNames(newVals);

  for (var idx = 0; idx < keys.length; idx++) {
    var key = keys[idx];
    var p = path.concat(key);

    if (newVals[key] === undefined && obj[key] !== undefined) {
      deletes.push(ObjectOperation.Delete(p, obj[key]));

    } else if (_.isObject(newVals[key])) {

      // TODO: for now, the structure must be the same
      if (!_.isObject(obj[key])) {
        throw new errors.ChronicleError("Incompatible arguments: newVals must have same structure as obj.");
      }
      __extend__(obj[key], newVals[key], p, deletes, creates, updates);

    } else {
      if (obj[key] === undefined) {
        creates.push(ObjectOperation.Create(p, newVals[key]));
      } else {
        var oldVal = obj[key];
        var newVal = newVals[key];
        if (!_.isEqual(oldVal, newVal)) {
          updates.push(ObjectOperation.Set(p, oldVal, newVal));
        }
      }
    }
  }
};

ObjectOperation.Extend = function(obj, newVals) {
  var deletes = [];
  var creates = [];
  var updates = [];
  __extend__(obj, newVals, [], deletes, creates, updates);
  return ObjectOperation.Compound(deletes.concat(creates).concat(updates));
};

// Export
// ========

if (typeof exports !== 'undefined') {
  module.exports = ObjectOperation;
} else {
  Chronicle.ot = Chronicle.ot || {};
  Chronicle.ot.ObjectOperation = ObjectOperation;
}

})(this);
<|endoftext|>


<commit_before>/*
 * node-thrust: thrust.js
 *
 * Copyright (c) 2014, Stanislas Polu. All rights reserved.
 *
 * @author: spolu
 *
 * @log:
 * - 2014-10-09 spolu  Creation
 */
"use strict"

var common = require('./common.js');

var async = require('async');
var path = require('path');
var os = require('os');

// ## thrust
//
// Main node-thrust entry point. See `spawn` method.
//
// ```
// @spec {}
// @inherits {}
// ```
var thrust = function(spec, my) {
  var _super = {};
  my = my || {};
  spec = spec || {};

  my.THRUST_PATH = path.join(__dirname, '..', 'vendor', 'thrust');
  my.THRUST_EXEC = {
    'linux': path.join(my.THRUST_PATH, 'thrust'),
    'win32': path.join(my.THRUST_PATH, 'thrust'),
    'darwin': path.join(my.THRUST_PATH,
                        'Thrust.app', 'Contents', 'MacOS', 'Atom'),
  }

  //
  // #### _public_
  //
  var spawn;             /* spawn(cb_); */

  //
  // #### _private_
  //

  //
  // #### _that_
  //
  var that = {};

  /****************************************************************************/
  /* PRIVATE HELPERS */
  /****************************************************************************/

  /****************************************************************************/
  /* PUBLIC METHODS */
  /****************************************************************************/
  // ### spawn
  //
  // Spawns a new `thrust` instace and returns an associated `api` object
  // to access its API. The `api` object communicates with the shell process
  // using unix domain socket.
  //
  // This method can be called multiple times to spawn different instances.
  // ```
  // @cb_ {function(err, api)}
  // ```
  spawn = function(cb_) {
    if(!my.THRUST_EXEC[os.platform()]) {
      return cb_(common.err('Platform not supported: ' + os.platform(),
                            'thrust:platform_not_supported'));
    }

    /*
    var p = require('child_process').spawn(my.THRUST_EXEC[os.platform()],
                                           [api.thrust_sock()],
                                           { stdio: 'inherit' });
    api.set_process(p);
    */
    require('./api.js').api({}).init(cb_);
  };

  common.method(that, 'spawn', spawn, _super);

  return that;
};

exports.thrust = thrust;

<commit_msg>WiP exec<commit_after>/*
 * node-thrust: thrust.js
 *
 * Copyright (c) 2014, Stanislas Polu. All rights reserved.
 *
 * @author: spolu
 *
 * @log:
 * - 2014-10-09 spolu  Creation
 */
"use strict"

var common = require('./common.js');

var async = require('async');
var path = require('path');
var os = require('os');

// ## thrust
//
// Main node-thrust entry point. See `spawn` method.
//
// ```
// @spec {}
// @inherits {}
// ```
var thrust = function(spec, my) {
  var _super = {};
  my = my || {};
  spec = spec || {};

  my.THRUST_PATH = path.join(__dirname, '..', 'vendor', 'thrust');
  my.THRUST_EXEC = {
    'linux': path.join(my.THRUST_PATH, 'thrust_shell'),
    'win32': path.join(my.THRUST_PATH, 'thrust_shell'),
    'darwin': path.join(my.THRUST_PATH,
                        'ThrustShell.app', 'Contents', 'MacOS', 'ThrustShell'),
  }

  //
  // #### _public_
  //
  var spawn;             /* spawn(cb_); */

  //
  // #### _private_
  //

  //
  // #### _that_
  //
  var that = {};

  /****************************************************************************/
  /* PRIVATE HELPERS */
  /****************************************************************************/

  /****************************************************************************/
  /* PUBLIC METHODS */
  /****************************************************************************/
  // ### spawn
  //
  // Spawns a new `thrust` instace and returns an associated `api` object
  // to access its API. The `api` object communicates with the shell process
  // using unix domain socket.
  //
  // This method can be called multiple times to spawn different instances.
  // ```
  // @cb_ {function(err, api)}
  // ```
  spawn = function(cb_) {
    if(!my.THRUST_EXEC[os.platform()]) {
      return cb_(common.err('Platform not supported: ' + os.platform(),
                            'thrust:platform_not_supported'));
    }

    /*
    var p = require('child_process').spawn(my.THRUST_EXEC[os.platform()],
                                           [api.thrust_sock()],
                                           { stdio: 'inherit' });
    api.set_process(p);
    */
    require('./api.js').api({}).init(cb_);
  };

  common.method(that, 'spawn', spawn, _super);

  return that;
};

exports.thrust = thrust;

<|endoftext|>


<commit_before>var _ = require('lodash');
var Q = require('q');
var url = require('url');
var Twilio = require('twilio');

var config = require('./config');
var profile = require('./profiles')[config.profile];

var client = Twilio(config.twilio.sid, config.twilio.token);

// Return an action url
function getActionUrl(action) {
	return config.host+'/twiml/'+action;
}

// TwilioML Response builder
function responseBuilder(fn, opts) {
	opts = _.defaults(opts || {}, {
		valid: true
	});

	return function(req, res, next) {
		var resp = new Twilio.TwimlResponse();
		var rurl = url.resolve(config.host, req.originalUrl);

		Q()
		.then(function() {
			if (!opts.valid) return;

			if (!Twilio.validateExpressRequest(req, config.twilio.token, { url: rurl })) {
				var e = new Error("Twilio Request Validation Failed.")
		        e.status = 403;
		        throw e;
		    }
		})
		.then(function() {
			return fn(resp, req.body, req, res);
		})
		.then(function() {
			res.set('Content-Type', 'text/xml');
	    	res.status(200).send(resp.toString());
		})
		.fail(next);
	};
}

module.exports = {
	twimlResponse: responseBuilder,
	actionUrl: getActionUrl
};
<commit_msg>Debug<commit_after>var _ = require('lodash');
var Q = require('q');
var url = require('url');
var Twilio = require('twilio');

var config = require('./config');
var profile = require('./profiles')[config.profile];

var client = Twilio(config.twilio.sid, config.twilio.token);

// Return an action url
function getActionUrl(action) {
	return config.host+'/twiml/'+action;
}

// TwilioML Response builder
function responseBuilder(fn, opts) {
	opts = _.defaults(opts || {}, {
		valid: true
	});

	return function(req, res, next) {
		var resp = new Twilio.TwimlResponse();
		var rurl = url.resolve(config.host, req.originalUrl);

		Q()
		.then(function() {
			if (!opts.valid) return;

			console.log(config.twilio.token, url)
			if (!Twilio.validateExpressRequest(req, config.twilio.token, { url: rurl })) {
				var e = new Error("Twilio Request Validation Failed.")
		        e.status = 403;
		        throw e;
		    }
		})
		.then(function() {
			return fn(resp, req.body, req, res);
		})
		.then(function() {
			res.set('Content-Type', 'text/xml');
	    	res.status(200).send(resp.toString());
		})
		.fail(next);
	};
}

module.exports = {
	twimlResponse: responseBuilder,
	actionUrl: getActionUrl
};
<|endoftext|>


<commit_before>'use strict';
const logger = require('winston');
const config = require('../config');
const utils = require('./utilities');
const request = require('request');

logger.debug('Loading TwitchLive Module');

let lastState = null;

module.exports = function TwitchLive(client) {
  function start() {
    logger.info('Starting Twitch Client');

    setTimeout(() => {
      checkLive(client);
    }, 1000 * 5);

    setInterval(() => {
      checkLive(client);
    }, 1000 * 120);
  }

  async function getUserLogo(id) {
    const uri = `https://api.twitch.tv/kraken/users/${id}?api_version=5&client_id=${config.twitch.client_id}`;
    logger.debug(uri);
    const response = await requestJSON(uri);
    logger.debug(response);
    return response.logo || 'https://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_70x70.png';
  }

  return {
    start,
    getUserLogo,
  };
};

async function checkLive(client) {
  let results;
  try {
    results = await client.mongo.twitchChannels.find({});
  } catch (err) {
    utils.ownerError('Twitch Client', client, err);
    return;
  }
  if (results.length === 0) return;
  // Filter and map only twitch ids that have an available channel to join
  const ids = results.filter(r => {
    for (let i = 0; i < r.channels.length; i++) {
      const channel = client.channels.get(r.channels[i].channel_id);
      if (channel) return true;
    }
    return false;
  }).map(r => r.twitch_id);
  client.twitch.count.users = ids.length;
  client.twitch.count.channels = results.filter(r => ids.indexOf(r.twitch_id) !== -1)
    .reduce((a, b) => {
      const channels = b.channels.map(c => client.channels.get(c.channel_id)).filter(x => x);
      return a + channels.length;
    }, 0);
  const idsArray = [];
  while (ids.length > 100) {
    idsArray.push(ids.splice(0, 100));
  }
  idsArray.push(ids);
  const promiseArray = idsArray.map(i => {
    const uri = `https://api.twitch.tv/kraken/streams?channel=${i.join(',')}` +
      `&stream_type=live&client_id=${config.twitch.client_id}&api_version=5`;
    logger.debug(uri);
    return requestJSON(uri);
  });
  Promise.all(promiseArray)
    .then(promiseResults => {
      // Empty waiting queue
      client.twitch.waiting = [];
      // Reduce to an array of twitch stream objects
      const merged = [].concat(...promiseResults)
        .reduce((a, b) => a.concat(...b.streams), []);
      // Save state on first connect then check differences every time after
      if (lastState) {
        merged.forEach(stream => {
          const id = stream.channel._id.toString();
          const mongoData = results.find(x => x.twitch_id === id);
          if (!mongoData) return;
          const inLastState = lastState.find(x => x.channel._id.toString() === id);
          if (inLastState) {
            const lastGame = inLastState.game;
            if (lastGame !== stream.game) {
              post(client, mongoData, stream, lastGame);
            }
          } else {
            post(client, mongoData, stream);
          }
        });
      }
      lastState = merged;
    })
    .catch(err => {
      logger.debug('Twitch Client Error', err);
      utils.ownerError('Twitch Client', client, err);
    });
}

async function post(client, mongoData, stream, oldGame) {
  client.count.twitch++;
  const name = utils.twitchDisplayName(stream.channel.name, stream.channel.display_name);
  const logo = stream.channel.logo || 'https://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_70x70.png';
  const embed = new client.Discord.RichEmbed()
    .setColor(await utils.palette(logo))
    .setThumbnail(logo)
    .addField('Twitch Channel:', `[${name}](${stream.channel.url})`, true)
    .addField('Status:', oldGame ? 'Changed Games' : 'Started Streaming', true);
  if (oldGame) {
    embed.addField('New Game:', game(stream.game), true)
      .addField('Old Game:', game(oldGame), true);
  } else {
    embed.addField('Game:', game(stream.game));
  }
  if (stream.channel.status) {
    embed.addField('Title:', stream.channel.status);
  }
  mongoData.channels.map(c => client.channels.get(c.channel_id))
    .filter(c => c)
    .forEach(c => {
      if (!c.guild.available) return;
      if (!c.permissionsFor(client.user).has('SEND_MESSAGES')) {
        client.logger.error(`Not allowed to post messages in guild **${c.guild.name}** channel **${c.name}** ` +
          `owned by **${c.guild.owner.user.username}**`);
        return;
      }
      c.send({ embed })
        .catch(err => {
          utils.ownerError(`Twitch Client Send to Channel (${c.id}) `, client, err);
        });
    });
}

function game(g) {
  if (g === '') {
    return '__NONE__';
  } else {
    return g;
  }
}

function requestJSON(uri) {
  return new Promise((resolve, reject) => {
    request.get({
      url: encodeURI(uri),
      json: true,
    }, (err, res, body) => {
      if (err) return reject(err);
      if (res.statusCode === 200 && body.error) return reject(body.error);
      if (res.statusCode !== 200) return reject(res.statusCode);
      return resolve(body);
    });
  });
}
<commit_msg>Needs addition permissions to send embeds<commit_after>'use strict';
const logger = require('winston');
const config = require('../config');
const utils = require('./utilities');
const request = require('request');

logger.debug('Loading TwitchLive Module');

let lastState = null;

module.exports = function TwitchLive(client) {
  function start() {
    logger.info('Starting Twitch Client');

    setTimeout(() => {
      checkLive(client);
    }, 1000 * 5);

    setInterval(() => {
      checkLive(client);
    }, 1000 * 120);
  }

  async function getUserLogo(id) {
    const uri = `https://api.twitch.tv/kraken/users/${id}?api_version=5&client_id=${config.twitch.client_id}`;
    logger.debug(uri);
    const response = await requestJSON(uri);
    logger.debug(response);
    return response.logo || 'https://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_70x70.png';
  }

  return {
    start,
    getUserLogo,
  };
};

async function checkLive(client) {
  let results;
  try {
    results = await client.mongo.twitchChannels.find({});
  } catch (err) {
    utils.ownerError('Twitch Client', client, err);
    return;
  }
  if (results.length === 0) return;
  // Filter and map only twitch ids that have an available channel to join
  const ids = results.filter(r => {
    for (let i = 0; i < r.channels.length; i++) {
      const channel = client.channels.get(r.channels[i].channel_id);
      if (channel) return true;
    }
    return false;
  }).map(r => r.twitch_id);
  client.twitch.count.users = ids.length;
  client.twitch.count.channels = results.filter(r => ids.indexOf(r.twitch_id) !== -1)
    .reduce((a, b) => {
      const channels = b.channels.map(c => client.channels.get(c.channel_id)).filter(x => x);
      return a + channels.length;
    }, 0);
  const idsArray = [];
  while (ids.length > 100) {
    idsArray.push(ids.splice(0, 100));
  }
  idsArray.push(ids);
  const promiseArray = idsArray.map(i => {
    const uri = `https://api.twitch.tv/kraken/streams?channel=${i.join(',')}` +
      `&stream_type=live&client_id=${config.twitch.client_id}&api_version=5`;
    logger.debug(uri);
    return requestJSON(uri);
  });
  Promise.all(promiseArray)
    .then(promiseResults => {
      // Empty waiting queue
      client.twitch.waiting = [];
      // Reduce to an array of twitch stream objects
      const merged = [].concat(...promiseResults)
        .reduce((a, b) => a.concat(...b.streams), []);
      // Save state on first connect then check differences every time after
      if (lastState) {
        merged.forEach(stream => {
          const id = stream.channel._id.toString();
          const mongoData = results.find(x => x.twitch_id === id);
          if (!mongoData) return;
          const inLastState = lastState.find(x => x.channel._id.toString() === id);
          if (inLastState) {
            const lastGame = inLastState.game;
            if (lastGame !== stream.game) {
              post(client, mongoData, stream, lastGame);
            }
          } else {
            post(client, mongoData, stream);
          }
        });
      }
      lastState = merged;
    })
    .catch(err => {
      logger.debug('Twitch Client Error', err);
      utils.ownerError('Twitch Client', client, err);
    });
}

async function post(client, mongoData, stream, oldGame) {
  client.count.twitch++;
  const name = utils.twitchDisplayName(stream.channel.name, stream.channel.display_name);
  const logo = stream.channel.logo || 'https://static-cdn.jtvnw.net/jtv_user_pictures/xarth/404_user_70x70.png';
  const embed = new client.Discord.RichEmbed()
    .setColor(await utils.palette(logo))
    .setThumbnail(logo)
    .addField('Twitch Channel:', `[${name}](${stream.channel.url})`, true)
    .addField('Status:', oldGame ? 'Changed Games' : 'Started Streaming', true);
  if (oldGame) {
    embed.addField('New Game:', game(stream.game), true)
      .addField('Old Game:', game(oldGame), true);
  } else {
    embed.addField('Game:', game(stream.game));
  }
  if (stream.channel.status) {
    embed.addField('Title:', stream.channel.status);
  }
  mongoData.channels.map(c => client.channels.get(c.channel_id))
    .filter(c => c)
    .forEach(c => {
      if (!c.guild.available) return;
      if (!c.permissionsFor(client.user).has(['SEND_MESSAGES', 'EMBED_LINKS'])) {
        client.logger.error(`Not allowed to post messages in guild **${c.guild.name}** channel **${c.name}** ` +
          `owned by **${c.guild.owner.user.username}**`);
        return;
      }
      c.send({ embed })
        .catch(err => {
          utils.ownerError(`Twitch Client Send to Channel (${c.id}) `, client, err);
        });
    });
}

function game(g) {
  if (g === '') {
    return '__NONE__';
  } else {
    return g;
  }
}

function requestJSON(uri) {
  return new Promise((resolve, reject) => {
    request.get({
      url: encodeURI(uri),
      json: true,
    }, (err, res, body) => {
      if (err) return reject(err);
      if (res.statusCode === 200 && body.error) return reject(body.error);
      if (res.statusCode !== 200) return reject(res.statusCode);
      return resolve(body);
    });
  });
}
<|endoftext|>


<commit_before>/*
	The Cedric's Swiss Knife (CSK) - CSK bench

	Copyright (c) 2016 Cédric Ronvel

	The MIT License (MIT)

	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.
*/

// Load modules
var async = require( 'async-kit' ) ;
var string = require( 'string-kit' ) ;
var termkit = require( 'terminal-kit' ) ;
var term = termkit.terminal ;

var notifications = require( 'freedesktop-notifications' ) ;
notifications.setAppName( 'wfm' ) ;

var minimist = require( 'minimist' ) ;
var glob = require( 'glob' ) ;

var ubenchPackage = require( '../package.json' ) ;

var ubench = {} ;
module.exports = ubench ;

ubench.cli = function cli()
{
	var i , iMax , args , package_ , benchFiles , context = {} ;

	var runtime = {
		warmupTimeout: 2000 ,
		coolDownTimeout: 2000 ,
		benchTimeout: 5000 ,
		benchFn: {}
	} ;

	term.bold.magenta( 'ubench' ).dim( ' v%s by Cédric Ronvel\n\n' , ubenchPackage.version ) ;

	args = minimist( process.argv.slice( 2 ) ) ;

	// Require the package.json (mandatory)
	try {
		package_ = require( process.cwd() + '/package.json' ) ;
	}
	catch ( error ) {
		if ( error.code === 'MODULE_NOT_FOUND' ) { ubench.exitError( "No package.json found.\n" ) ; }
		else { ubench.exitError( "Error in the package.json: %E\n" , error ) ; }
	}

	// Get the bench directory
	if ( ! package_.directories || ! package_.directories.bench )
	{
		ubench.exitError( "The package.json miss a directories.bench path, set to the directory containing bench files.\n" ) ;
	}

	// Register to global
	global.bench = ubench.registerBenchmark.bind( runtime ) ;

	// Load all bench files
	benchFiles = glob.sync( process.cwd() + '/' + package_.directories.bench + '/*js' ) ;

	try {
		for ( i = 0 , iMax = benchFiles.length ; i < iMax ; i ++ )
		{
			require( benchFiles[ i ] ) ;
		}
	}
	catch ( error ) {
		ubench.exitError( "Error in the bench file '" + benchFiles[ i ] + "': %E\n" , error ) ;
	}

	ubench.init( function() {
		ubench.run( runtime ) ;
	} ) ;
} ;

ubench.init = function init( callback )
{
	notifications.init( callback ) ;
} ;

ubench.usage = function usage()
{
	term( "Usage is: ubench [<batch>] [<test>] [<option1>] [<option2>] [...]\n" ) ;
} ;

ubench.exitError = function exitError()
{
	var message = string.format.apply( null , arguments ) ;

	term.bold.red( message ) ;

	notifications.createNotification( {
		summary: 'ubench: error' ,
		body: message ,
		icon: 'dialog-warning'
	} ).push() ;

	process.exit( 1 ) ;
} ;

ubench.run = function run( runtime )
{
	async.foreach( runtime.benchFn , function( e , benchName , outerForeachCallback ) {

		term( '^y-- Running: ^Y^/%s^:^y --\n\n' , benchName ) ;

		async.map( e , function( benchFn , benchVariant , innerForeachCallback ) {

			var benchMethod = ubench.doOneBench ;

			term.column.eraseLine( 1 , "^M%s^m: ^Y^+WARMUP^:" , benchVariant ) ;

			benchMethod( benchFn , runtime.warmupTimeout , function() {

				term.column.eraseLine( 1 , "^M%s^m: ^C^+COOLDOWN^:" , benchVariant ) ;

				// Force the GC now!
				global.gc() ;

				// Force the GC again! Run that lazy V8!
				setTimeout( global.gc , runtime.coolDownTimeout / 2 ) ;

				setTimeout( function() {

						term.column.eraseLine( 1 , "^M%s^m: ^R^+RUNNING^:" , benchVariant ) ;

						startMemory = process.memoryUsage().heapUsed ;
						benchMethod( benchFn , runtime.benchTimeout , function( error , result ) {

							term.column.eraseLine( 1 , "^M%s^m: ^C^+COOLDOWN^:" , benchVariant ) ;

							// Force the GC now!
							global.gc() ;
							endMemory = process.memoryUsage().heapUsed ;
							result.

							setTimeout( innerForeachCallback.bind( undefined , error , result ) , runtime.coolDownTimeout ) ;
						} ) ;
					} , runtime.coolDownTimeout
				) ;
			} ) ;
		} )
		.parallel( 1 )
		.exec( function( error , results ) {

			var benchVariant , fastest = 0 , col = 0 , paddingOps = 0 , paddingMem = 0 , numOps , numMem , percent ;

			if ( error ) { outerForeachCallback( error ) ; return ; }

			for ( benchVariant in results )
			{
				if ( results[ benchVariant ].opsPerSecond > fastest ) { fastest = results[ benchVariant ].opsPerSecond ; }
				if ( benchVariant.length > col ) { col = benchVariant.length ; }

				numOps = '' + results[ benchVariant ].opsPerSecond ;
				if ( numOps.length > paddingOps ) { paddingOps = numOps.length ; }

				numMem = '' + results[ benchVariant ].memoryBalance ;
				if ( numMem.length > paddingMem ) { paddingMem = numMem.length ; }
			}

			col += 10 ;

			for ( benchVariant in results )
			{
				term.column.eraseLine( 1 , '    ^m* ^M^/%s^:^m : ' , benchVariant ) ;

				numOps = '' + results[ benchVariant ].opsPerSecond ;
				if ( numOps.length < paddingOps ) { numOps = ' '.repeat( paddingOps - numOps.length ) + numOps ; }

				term.column( col , '^B%s^b ops/s   ' , numOps ) ;

				if ( results[ benchVariant ].opsPerSecond < fastest )
				{
					percent = '' + Math.round( 100 * ( 1 - results[ benchVariant ].opsPerSecond / fastest ) ) ;
					percent = ' '.repeat( 3 - percent.length ) + percent ;
					term( '^B%s%% slower' , percent ) ;
				}
				else
				{
					term( '  ^R^+^_FASTEST^:  ' ) ;
				}

				numMem = '' + results[ benchVariant ].memoryBalance ;
				if ( numMem.length < paddingMem ) { numMem = ' '.repeat( paddingMem - numMem.length ) + numMem ; }

				term( '   ^B%s^b memory balance   ' , numMem ) ;

				/*
				if ( results[ benchVariant ].opsPerSecond < fastest )
				{
					term( '^B%i%% slower' , 100 * ( 1 - results[ benchVariant ].opsPerSecond / fastest ) ) ;
				}
				else
				{
					term( '^R^+^_FASTEST^:' ) ;
				}
				*/

				term( '^:\n' ) ;
			}

			outerForeachCallback() ;
		} ) ;

	} )
	.exec( function( error ) {
		term.styleReset() ;
		term( '\n' ) ;
		process.exit() ;
	} ) ;
} ;

ubench.doOneBench = function doOneBench( benchFn , timeout , callback )
{
	var count , startTime , endTime , maxEndTime , startMemory , endMemory ;

	startMemory = process.memoryUsage().heapUsed ;

	// We do not want to mesure the for loop speed, so we run at least 100 times the bench function
	for ( count = 0 , startTime = Date.now() , maxEndTime = startTime + timeout ; ( endTime = Date.now() ) < maxEndTime ; count += 100 )
	{
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
	}

	endMemory = process.memoryUsage().heapUsed ;

	//console.log( count , startTime , endTime , endTime - startTime ) ;
	callback( undefined , {
		opsPerSecond: 1000 * count / ( endTime - startTime ) ,
		memory: endMemory - startMemory
	} ) ;
} ;

			/* User-land global functions */

ubench.registerBenchmark = function()
{
	var benchName , benchVariant , benchFn ;

	if ( arguments.length < 2 ) { throw new Error( "bench() need at least 2 arguments" ) ; }

	benchName = arguments[ 0 ] ;

	if ( arguments.length > 2 )
	{
		benchVariant = arguments[ 1 ] ;
		benchFn = arguments[ 2 ] ;
	}
	else
	{
		benchFn = arguments[ 1 ] ;
		benchVariant = benchFn.name || '' ;
	}

	if ( ! this.benchFn[ benchName ] ) { this.benchFn[ benchName ] = {} ; }

	this.benchFn[ benchName ][ benchVariant ] = benchFn ;

	//console.log( 'Registered:' , benchName , benchVariant ) ;
} ;

<commit_msg>can't understand why V8 do not GC on demand...<commit_after>/*
	The Cedric's Swiss Knife (CSK) - CSK bench

	Copyright (c) 2016 Cédric Ronvel

	The MIT License (MIT)

	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.
*/

// Load modules
var async = require( 'async-kit' ) ;
var string = require( 'string-kit' ) ;
var termkit = require( 'terminal-kit' ) ;
var term = termkit.terminal ;

var notifications = require( 'freedesktop-notifications' ) ;
notifications.setAppName( 'wfm' ) ;

var minimist = require( 'minimist' ) ;
var glob = require( 'glob' ) ;

var ubenchPackage = require( '../package.json' ) ;

var ubench = {} ;
module.exports = ubench ;

ubench.cli = function cli()
{
	var i , iMax , args , package_ , benchFiles , context = {} ;

	var runtime = {
		warmupTimeout: 2000 ,
		coolDownTimeout: 2000 ,
		benchTimeout: 5000 ,
		benchFn: {}
	} ;

	term.bold.magenta( 'ubench' ).dim( ' v%s by Cédric Ronvel\n\n' , ubenchPackage.version ) ;

	args = minimist( process.argv.slice( 2 ) ) ;

	// Require the package.json (mandatory)
	try {
		package_ = require( process.cwd() + '/package.json' ) ;
	}
	catch ( error ) {
		if ( error.code === 'MODULE_NOT_FOUND' ) { ubench.exitError( "No package.json found.\n" ) ; }
		else { ubench.exitError( "Error in the package.json: %E\n" , error ) ; }
	}

	// Get the bench directory
	if ( ! package_.directories || ! package_.directories.bench )
	{
		ubench.exitError( "The package.json miss a directories.bench path, set to the directory containing bench files.\n" ) ;
	}

	// Register to global
	global.bench = ubench.registerBenchmark.bind( runtime ) ;

	// Load all bench files
	benchFiles = glob.sync( process.cwd() + '/' + package_.directories.bench + '/*js' ) ;

	try {
		for ( i = 0 , iMax = benchFiles.length ; i < iMax ; i ++ )
		{
			require( benchFiles[ i ] ) ;
		}
	}
	catch ( error ) {
		ubench.exitError( "Error in the bench file '" + benchFiles[ i ] + "': %E\n" , error ) ;
	}

	ubench.init( function() {
		ubench.run( runtime ) ;
	} ) ;
} ;

ubench.init = function init( callback )
{
	notifications.init( callback ) ;
} ;

ubench.usage = function usage()
{
	term( "Usage is: ubench [<batch>] [<test>] [<option1>] [<option2>] [...]\n" ) ;
} ;

ubench.exitError = function exitError()
{
	var message = string.format.apply( null , arguments ) ;

	term.bold.red( message ) ;

	notifications.createNotification( {
		summary: 'ubench: error' ,
		body: message ,
		icon: 'dialog-warning'
	} ).push() ;

	process.exit( 1 ) ;
} ;

ubench.run = function run( runtime )
{
	async.foreach( runtime.benchFn , function( e , benchName , outerForeachCallback ) {

		term( '^y-- Running: ^Y^/%s^:^y --\n\n' , benchName ) ;

		async.map( e , function( benchFn , benchVariant , innerForeachCallback ) {

			var benchMethod = ubench.doOneBench ;

			term.column.eraseLine( 1 , "^M%s^m: ^Y^+WARMUP^:" , benchVariant ) ;

			benchMethod( benchFn , runtime.warmupTimeout , function() {

				term.column.eraseLine( 1 , "^M%s^m: ^C^+COOLDOWN^:" , benchVariant ) ;

				// Force the GC now!
				global.gc() ;

				// Force the GC again! Run that lazy V8!
				setTimeout( global.gc , runtime.coolDownTimeout / 2 ) ;

				setTimeout( function() {

						//var startMemory , endMemory ;

						term.column.eraseLine( 1 , "^M%s^m: ^R^+RUNNING^:" , benchVariant ) ;

						//startMemory = process.memoryUsage().heapUsed ;
						benchMethod( benchFn , runtime.benchTimeout , function( error , result ) {

							term.column.eraseLine( 1 , "^M%s^m: ^C^+COOLDOWN^:" , benchVariant ) ;

							// Force the GC now!
							global.gc() ;
							//endMemory = process.memoryUsage().heapUsed ;
							//result.memoryLeak = endMemory - startMemory ;

							setTimeout( innerForeachCallback.bind( undefined , error , result ) , runtime.coolDownTimeout ) ;
						} ) ;
					} , runtime.coolDownTimeout
				) ;
			} ) ;
		} )
		.parallel( 1 )
		.exec( function( error , results ) {

			var benchVariant , fastest = 0 , col = 0 , percent ,
				paddingOps = 0 , paddingMem = 0 , paddingLeak = 0 ,
				numOps , numMem , numLeak ;

			if ( error ) { outerForeachCallback( error ) ; return ; }

			for ( benchVariant in results )
			{
				if ( results[ benchVariant ].opsPerSecond > fastest ) { fastest = results[ benchVariant ].opsPerSecond ; }
				if ( benchVariant.length > col ) { col = benchVariant.length ; }

				numOps = '' + results[ benchVariant ].opsPerSecond ;
				if ( numOps.length > paddingOps ) { paddingOps = numOps.length ; }

				numMem = '' + results[ benchVariant ].memory ;
				if ( numMem.length > paddingMem ) { paddingMem = numMem.length ; }

				numLeak = '' + results[ benchVariant ].memoryLeak ;
				if ( numLeak.length > paddingLeak ) { paddingLeak = numLeak.length ; }
			}

			col += 10 ;

			for ( benchVariant in results )
			{
				term.column.eraseLine( 1 , '    ^m* ^M^/%s^:^m : ' , benchVariant ) ;

				numOps = '' + results[ benchVariant ].opsPerSecond ;
				if ( numOps.length < paddingOps ) { numOps = ' '.repeat( paddingOps - numOps.length ) + numOps ; }

				term.column( col , '^B%s^b ops/s   ' , numOps ) ;

				if ( results[ benchVariant ].opsPerSecond < fastest )
				{
					percent = '' + Math.round( 100 * ( 1 - results[ benchVariant ].opsPerSecond / fastest ) ) ;
					percent = ' '.repeat( 3 - percent.length ) + percent ;
					term( '^B%s%% slower' , percent ) ;
				}
				else
				{
					term( '  ^R^+^_FASTEST^:  ' ) ;
				}

				numMem = '' + results[ benchVariant ].memory ;
				if ( numMem.length < paddingMem ) { numMem = ' '.repeat( paddingMem - numMem.length ) + numMem ; }
				term( '   ^B%s^b memory   ' , numMem ) ;

				numLeak = '' + results[ benchVariant ].memoryLeak ;
				if ( numLeak.length < paddingLeak ) { numLeak = ' '.repeat( paddingLeak - numLeak.length ) + numLeak ; }
				term( '   ^B%s^b leak   ' , numLeak ) ;

				term( '^:\n' ) ;
			}

			outerForeachCallback() ;
		} ) ;

	} )
	.exec( function( error ) {
		term.styleReset() ;
		term( '\n' ) ;
		process.exit() ;
	} ) ;
} ;

ubench.doOneBench = function doOneBench( benchFn , timeout , callback )
{
	var count , startTime , endTime , maxEndTime , startMemory , endMemory ;

	startMemory = process.memoryUsage().heapUsed ;

	// We do not want to mesure the for loop speed, so we run at least 100 times the bench function
	for ( count = 0 , startTime = Date.now() , maxEndTime = startTime + timeout ; ( endTime = Date.now() ) < maxEndTime ; count += 100 )
	{
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
		benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ; benchFn() ;
	}

	endMemory = process.memoryUsage().heapUsed ;

	//console.log( count , startTime , endTime , endTime - startTime ) ;
	callback( undefined , {
		opsPerSecond: 1000 * count / ( endTime - startTime ) ,
		memory: endMemory - startMemory
	} ) ;
} ;

			/* User-land global functions */

ubench.registerBenchmark = function()
{
	var benchName , benchVariant , benchFn ;

	if ( arguments.length < 2 ) { throw new Error( "bench() need at least 2 arguments" ) ; }

	benchName = arguments[ 0 ] ;

	if ( arguments.length > 2 )
	{
		benchVariant = arguments[ 1 ] ;
		benchFn = arguments[ 2 ] ;
	}
	else
	{
		benchFn = arguments[ 1 ] ;
		benchVariant = benchFn.name || '' ;
	}

	if ( ! this.benchFn[ benchName ] ) { this.benchFn[ benchName ] = {} ; }

	this.benchFn[ benchName ][ benchVariant ] = benchFn ;

	//console.log( 'Registered:' , benchName , benchVariant ) ;
} ;

<|endoftext|>


<commit_before>'use strict'

var util = require('util')
var progress = require('progress-string')
var diff = require('ansi-diff-stream')()
var speedometer = require('speedometer')
var prettierBytes = require('prettier-bytes')
var Updater = require('../updater')
var debug = require('debug')(require('../package').name)

module.exports = function (db, opts) {
  var downloadSpeed = speedometer()
  var processingSpeed = speedometer()
  var updater = new Updater(db, opts)
  var lastFeedLength = 0
  var downloaded = 0
  var remaining = 0
  var bps = 0
  var pps = 0

  updater
    .on('init', trackSyncing)
    .on('running', function () {
      lastFeedLength = updater.feed.length
      remaining = updater.blocksRemaining()
    })
    .on('processed', function () {
      pps = processingSpeed(1)
    })
    .on('end', function () {
      printProgress()
      process.exit(updater.live ? 1 : 0)
    })
    .on('error', function (err) {
      throw err
    })

  if (!debug.enabled) diff.pipe(process.stdout)

  function trackSyncing () {
    updater.feed.on('download', function (index, data) {
      bps = downloadSpeed(data.length)
      downloaded++
      if (updater.feed.length > lastFeedLength) {
        remaining += updater.feed.length - lastFeedLength
        lastFeedLength = updater.feed.length
        debug('feed.length changed')
      }
    })

    setInterval(function () {
      bps = downloadSpeed(0)
      pps = processingSpeed(0)
    }, 1000).unref()

    setInterval(printProgress, 250).unref()
  }

  function printProgress () {
    var processedCounter = updater.processed - updater.startBlock + 1

    if (updater.live) {
      diff.write(
        util.format('downloaded %d [%s/s]\n', downloaded, prettierBytes(bps)) +
        util.format('processed  %d [%s/sec]', processedCounter, pps.toFixed(1))
      )
    } else {
      var totalToProcess = updater.feed.length - updater.startBlock
      var sessionBar = progress({total: remaining})
      var processedBar = progress({total: totalToProcess})

      diff.write(
        util.format('downloading [%s] (%d/%d) %s/s\n', sessionBar(downloaded), downloaded, remaining, prettierBytes(bps)) +
        util.format('processing  [%s] (%d/%d) %s/sec', processedBar(processedCounter), processedCounter, totalToProcess, pps.toFixed(1))
      )
    }
  }
}
<commit_msg>List downloaded pkg/s when updating<commit_after>'use strict'

var util = require('util')
var progress = require('progress-string')
var diff = require('ansi-diff-stream')()
var speedometer = require('speedometer')
var prettierBytes = require('prettier-bytes')
var Updater = require('../updater')
var debug = require('debug')(require('../package').name)

module.exports = function (db, opts) {
  var downloadByteSpeed = speedometer()
  var downloadBlockSpeed = speedometer()
  var processingSpeed = speedometer()
  var updater = new Updater(db, opts)
  var lastFeedLength = 0
  var downloaded = 0
  var remaining = 0
  var bps = 0
  var pkgps = 0
  var pps = 0

  updater
    .on('init', trackSyncing)
    .on('running', function () {
      lastFeedLength = updater.feed.length
      remaining = updater.blocksRemaining()
    })
    .on('processed', function () {
      pps = processingSpeed(1)
    })
    .on('end', function () {
      printProgress()
      process.exit(updater.live ? 1 : 0)
    })
    .on('error', function (err) {
      throw err
    })

  if (!debug.enabled) diff.pipe(process.stdout)

  function trackSyncing () {
    updater.feed.on('download', function (index, data) {
      bps = downloadByteSpeed(data.length)
      pkgps = downloadBlockSpeed(1)
      downloaded++
      if (updater.feed.length > lastFeedLength) {
        remaining += updater.feed.length - lastFeedLength
        lastFeedLength = updater.feed.length
        debug('feed.length changed')
      }
    })

    setInterval(function () {
      bps = downloadByteSpeed(0)
      pkgps = downloadBlockSpeed(0)
      pps = processingSpeed(0)
    }, 1000).unref()

    setInterval(printProgress, 250).unref()
  }

  function printProgress () {
    var processedCounter = updater.processed - updater.startBlock + 1

    if (updater.live) {
      diff.write(
        util.format('downloaded %d [%s pkg/s, %s/s]\n', downloaded, pkgps.toFixed(1), prettierBytes(bps)) +
        util.format('processed  %d [%s pkg/s]', processedCounter, pps.toFixed(1))
      )
    } else {
      var totalToProcess = updater.feed.length - updater.startBlock
      var sessionBar = progress({total: remaining})
      var processedBar = progress({total: totalToProcess})

      diff.write(
        util.format('downloading [%s] (%d/%d) %s pkg/s, %s/s\n', sessionBar(downloaded), downloaded, remaining, pkgps.toFixed(1), prettierBytes(bps)) +
        util.format('processing  [%s] (%d/%d) %s pkg/s', processedBar(processedCounter), processedCounter, totalToProcess, pps.toFixed(1))
      )
    }
  }
}
<|endoftext|>


<commit_before>'use strict'

const pkg = require('../package.json')
const Configstore = require('configstore')
const defaultConfig = require('../defaultConfig')
const neonjs = require('neon-js')
const chalk = require('chalk')
const q = require('q')
const asTable = require('as-table').configure({
  title: x => chalk.bold.green(x),
  delimiter: chalk.dim.green(' | '),
  dash: chalk.dim.green('-'),
  print: obj => (typeof obj === 'boolean') ? (obj ? 'Yes' : 'No') : String(obj)
})
const network = require('./network')

const conf = new Configstore(pkg.name, defaultConfig)

function walletCreate(self, args, cb) {
  self.prompt([
    {
      type: 'input',
      name: 'label',
      message: chalk.green('? Give this address a label [optional] : ')
    },
    {
      type: 'password',
      name: 'passphrase',
      message: chalk.green('? Provide a passphrase : ')
    },
    {
      type: 'password',
      name: 'passphrase2',
      message: chalk.green('? Confirm your passphrase : ')
    }
  ], function (inputs) {
    if (inputs.passphrase.length <= 4) {
      self.log(' Passphrase must be longer than 4 characters.')
      cb()
    } else if (inputs.passphrase !== inputs.passphrase2) {
      self.log(' Passphrase and confirmation don\'t match.')
      cb()
    } else {
      let newPrivateKey = neonjs.generatePrivateKey()
      let newWif = neonjs.getWIFFromPrivateKey(newPrivateKey)
      encryptKey(self, newWif, inputs)
        .then((outputs) => {
          cb()
        })
    }
  })
}

function walletImport(self, args, cb) {
  self.prompt([
    {
      type: 'password',
      name: 'existingWif',
      message: chalk.green(' Enter your existing private key in WIF format : ')
    },
    {
      type: 'input',
      name: 'label',
      message: chalk.green('? Give this address a label [optional] : ')
    },
    {
      type: 'password',
      name: 'passphrase',
      message: chalk.green('? Provide a passphrase : ')
    },
    {
      type: 'password',
      name: 'passphrase2',
      message: chalk.green('? Confirm your passphrase : ')
    }
  ], function (inputs) {
    if (inputs.existingWif.toString().length != 52) {
      self.log(' Please provide a valid private key in WIF format.')
      cb()
    } else if (inputs.passphrase.length <= 4) {
      self.log(' Passphrase must be longer than 4 characters.')
      cb()
    } else if (inputs.passphrase !== inputs.passphrase2) {
      self.log(' Passphrase and confirmation don\'t match.')
      cb()
    } else {
      encryptKey(self, inputs.existingWif.toString(), inputs)
        .then((outputs) => {
          cb()
        })
    }
  })
}

function walletList(self, args, cb) {
  let tableData = [];
  let addresses = conf.get('addresses')
  let totalBalances = []
  if (addresses.length <= 0) {
    self.log(' No addresses found.')
  } else {
    let addressList = []
    for (let i = 0; i < addresses.length; i++) {
      let row = {
        Label: chalk.green(addresses[i].label),
        Address: chalk.green(addresses[i].address)
      }
      for (let asset in addresses[i].balances) {
        if (asset != 'unspent') {
          if (totalBalances[asset] == undefined) {
            totalBalances[asset] = 0
          }
          row[asset] = chalk.green(addresses[i].balances[asset])
          totalBalances[asset] = totalBalances[asset] + addresses[i].balances[asset]
        }
      }
      tableData.push(row)
    }
    let summaryRow = {
      Label: '',
      Address: chalk.dim.green('Totals')
    }
    for (let asset in totalBalances) {
      summaryRow[asset] = chalk.dim.green(totalBalances[asset])
    }
    tableData.push(summaryRow)
    self.log(asTable(tableData).toString());
  }
  cb()
}

function walletRemove(self, args, cb) {
  let addresses = conf.get('addresses')
  let addressList = []
  let removed = false
  if (addresses.length <= 0) {
    self.log(' No addresses found.')
    cb()
  } else {
    for (let i = 0; i < addresses.length; i++) {
      addressList.push((addresses[i].label ? addresses[i].label + ' (' : '') + addresses[i].address + (addresses[i].label ? ')' : ''))
    }
    self.prompt({
      type: 'list',
      name: 'addressSelection',
      message: chalk.green('Which address would you like to remove?'),
      choices: addressList
    }, (result) => {
      let address = result.addressSelection;
      if (address.indexOf('(') >= 0) {
        let foundAddress = address.match(/\((.*)\)/)
        address = foundAddress[1]
      }
      for (let i = 0; i < addresses.length; i++) {
        if (addresses[i].address == address) {
          addresses.splice(i, 1)
          self.log(chalk.green(' Removed ' + address))
          removed = true
        }
      }
      conf.set('addresses', addresses)
      if (!removed) {
        self.log(' ' + address + ' not found in local storage.')
      }
      cb()
    })
  }
}

function walletClear(self, args, cb) {
  self.prompt({
    type: 'confirm',
    name: 'doClear',
    message: chalk.bold.red('Are you certain you\'d like to purge locally saved addresses and encrypted private keys?'),
    default: false
  }, (result) => {
    if (result.doClear) {
      conf.set('addresses', [])
      self.log(chalk.white(' Wallet cleared.'))
    }
    cb()
  })
}

function encryptKey(self, newWif, inputs) {
  let deferred = q.defer();

  neonjs.encryptWifAccount(newWif, inputs.passphrase.toString())
    .then((newAccount) => {
      let wallet = {
        label: (inputs.label != undefined ? inputs.label : ''),
        address: newAccount.address,
        encryptedWif: newAccount.encryptedWif
      }
      let addresses = conf.get('addresses')
      addresses.push(wallet)
      conf.set('addresses', addresses);
      self.log(chalk.bold.green(' This information will only be displayed once!'))
      self.log(chalk.green(' Your address and encrypted private key will be stored locally on your machine.'))
      self.log(chalk.green(' Your private key and passphrase will not be stored anywhere.'))
      self.log(chalk.green(' Label: ') + chalk.bold.green(inputs.label))
      self.log(chalk.green(' Public Address: ') + chalk.bold.green(newAccount.address))
      self.log(chalk.green(' Private Key: ') + chalk.bold.green(newAccount.wif))
      self.log(chalk.green(' Encrypted Key: ') + chalk.bold.green(newAccount.encryptedWif))
      self.log(chalk.green(' Passphrase: ') + chalk.bold.green(newAccount.passphrase))
      self.log(chalk.bold.red(' Now is your only chance to make a secure copy of your private key!'))
      self.prompt({
        type: 'confirm',
        name: 'keysaved',
        message: chalk.green('Have you saved your private key securely? '),
        default: true
      }, function (answer) {
        self.log(chalk.green(' I must believe you have.'))
        deferred.resolve(answer)
      })
    })

  return deferred.promise;
}

function walletShow(self, args, cb) {
  let addresses = conf.get('addresses')
  let addressList = []
  let addressIndex
  if (addresses.length <= 0) {
    self.log(' No addresses found.')
    cb()
  } else {
    for (let i = 0; i < addresses.length; i++) {
      addressList.push((addresses[i].label ? addresses[i].label + ' (' : '') + addresses[i].address + (addresses[i].label ? ')' : ''))
    }
    self.prompt({
      type: 'list',
      name: 'addressSelection',
      message: chalk.green('Which address would you like to see?'),
      choices: addressList
    }, (result) => {
      let address = result.addressSelection;
      if (address.indexOf('(') >= 0) {
        let foundAddress = address.match(/\((.*)\)/)
        address = foundAddress[1]
      }
      for (let i = 0; i < addresses.length; i++) {
        if (addresses[i].address == address) {
          addressIndex = i
        }
      }
      let tableData = []
      for (let asset in addresses[addressIndex].balances) {
        if (asset != 'unspent') {
          let row = [
            chalk.green(asset),
            chalk.green(addresses[addressIndex].balances[asset])
          ]
          tableData.push(row)
        }
      }
      self.log(chalk.bold.green('Balances'))
      self.log(asTable(tableData).toString())

      let transactionHistory = neonjs.getTransactionHistory(network.get(), address)
        .then((txns) => {
          self.log('')
          if (txns.length == 0) {
            self.log(chalk.green(' No transactions found.'))
          } else {
            let txnData = []
            let txnQty = (txns.length <= 20 ? txns.length : 20)
            for (let i = 0; i < txnQty; i++) {
              let row = {
                Asset: chalk.green(txns[i].neo_sent ? 'Neo' : 'Gas'),
                Amount: chalk.green(txns[i][(txns[i].neo_sent ? 'NEO' : 'GAS')]),
                TransacionID: chalk.dim.green(txns[i].txid)
              }
              txnData.push(row)
            }
            self.log(asTable(txnData).toString())
            self.log(chalk.dim.green(txnQty + ' Most recent transactions'))
          }
        })
        .then(() => {
          let claimAmounts = neonjs.getClaimAmounts(network.get(), address)
            .then((claimables) => {
              self.log('')
              let gasToClaim = (claimables.available + claimables.unavailable) / 100000000
              self.log(chalk.bold.green('Claimable gas: ') + chalk.green(gasToClaim))
              cb()
            })
        })
    })
  }
}

function walletUpdateBalances(self) {
  let addresses = conf.get('addresses')
  for (let i = 0; i < addresses.length; i++) {
    neonjs.getBalance(network.get(), addresses[i].address)
      .then((result) => {
        addresses[i].balances = result
        conf.set('addresses', addresses)
      })
  }
}

module.exports.create = walletCreate
module.exports.import = walletImport
module.exports.list = walletList
module.exports.remove = walletRemove
module.exports.clear = walletClear
module.exports.show = walletShow
module.exports.updateBalances = walletUpdateBalances
<commit_msg>Update for new balance API<commit_after>'use strict'

const pkg = require('../package.json')
const Configstore = require('configstore')
const defaultConfig = require('../defaultConfig')
const neonjs = require('neon-js')
const chalk = require('chalk')
const q = require('q')
const asTable = require('as-table').configure({
  title: x => chalk.bold.green(x),
  delimiter: chalk.dim.green(' | '),
  dash: chalk.dim.green('-'),
  print: obj => (typeof obj === 'boolean') ? (obj ? 'Yes' : 'No') : String(obj)
})
const network = require('./network')

const conf = new Configstore(pkg.name, defaultConfig)

function walletCreate(self, args, cb) {
  self.prompt([
    {
      type: 'input',
      name: 'label',
      message: chalk.green('? Give this address a label [optional] : ')
    },
    {
      type: 'password',
      name: 'passphrase',
      message: chalk.green('? Provide a passphrase : ')
    },
    {
      type: 'password',
      name: 'passphrase2',
      message: chalk.green('? Confirm your passphrase : ')
    }
  ], function (inputs) {
    if (inputs.passphrase.length <= 4) {
      self.log(' Passphrase must be longer than 4 characters.')
      cb()
    } else if (inputs.passphrase !== inputs.passphrase2) {
      self.log(' Passphrase and confirmation don\'t match.')
      cb()
    } else {
      let newPrivateKey = neonjs.generatePrivateKey()
      let newWif = neonjs.getWIFFromPrivateKey(newPrivateKey)
      encryptKey(self, newWif, inputs)
        .then((outputs) => {
          cb()
        })
    }
  })
}

function walletImport(self, args, cb) {
  self.prompt([
    {
      type: 'password',
      name: 'existingWif',
      message: chalk.green(' Enter your existing private key in WIF format : ')
    },
    {
      type: 'input',
      name: 'label',
      message: chalk.green('? Give this address a label [optional] : ')
    },
    {
      type: 'password',
      name: 'passphrase',
      message: chalk.green('? Provide a passphrase : ')
    },
    {
      type: 'password',
      name: 'passphrase2',
      message: chalk.green('? Confirm your passphrase : ')
    }
  ], function (inputs) {
    if (inputs.existingWif.toString().length != 52) {
      self.log(' Please provide a valid private key in WIF format.')
      cb()
    } else if (inputs.passphrase.length <= 4) {
      self.log(' Passphrase must be longer than 4 characters.')
      cb()
    } else if (inputs.passphrase !== inputs.passphrase2) {
      self.log(' Passphrase and confirmation don\'t match.')
      cb()
    } else {
      encryptKey(self, inputs.existingWif.toString(), inputs)
        .then((outputs) => {
          cb()
        })
    }
  })
}

function walletList(self, args, cb) {
  let tableData = [];
  let addresses = conf.get('addresses')
  let totalBalances = []
  if (addresses.length <= 0) {
    self.log(' No addresses found.')
  } else {
    let addressList = []
    for (let i = 0; i < addresses.length; i++) {
      let row = {
        Label: chalk.green(addresses[i].label),
        Address: chalk.green(addresses[i].address)
      }
      for (let asset in addresses[i].balances) {
        if (asset != 'address' && asset != 'net') {
          if (totalBalances[asset] == undefined) {
            totalBalances[asset] = 0
          }
          row[asset] = chalk.green(addresses[i].balances[asset].balance)
          totalBalances[asset] = totalBalances[asset] + addresses[i].balances[asset].balance
        }
      }
      tableData.push(row)
    }
    let summaryRow = {
      Label: '',
      Address: chalk.dim.green('Totals')
    }
    for (let asset in totalBalances) {
      summaryRow[asset] = chalk.dim.green(totalBalances[asset])
    }
    tableData.push(summaryRow)
    self.log(asTable(tableData).toString());
  }
  cb()
}

function walletRemove(self, args, cb) {
  let addresses = conf.get('addresses')
  let addressList = []
  let removed = false
  if (addresses.length <= 0) {
    self.log(' No addresses found.')
    cb()
  } else {
    for (let i = 0; i < addresses.length; i++) {
      addressList.push((addresses[i].label ? addresses[i].label + ' (' : '') + addresses[i].address + (addresses[i].label ? ')' : ''))
    }
    self.prompt({
      type: 'list',
      name: 'addressSelection',
      message: chalk.green('Which address would you like to remove?'),
      choices: addressList
    }, (result) => {
      let address = result.addressSelection;
      if (address.indexOf('(') >= 0) {
        let foundAddress = address.match(/\((.*)\)/)
        address = foundAddress[1]
      }
      for (let i = 0; i < addresses.length; i++) {
        if (addresses[i].address == address) {
          addresses.splice(i, 1)
          self.log(chalk.green(' Removed ' + address))
          removed = true
        }
      }
      conf.set('addresses', addresses)
      if (!removed) {
        self.log(' ' + address + ' not found in local storage.')
      }
      cb()
    })
  }
}

function walletClear(self, args, cb) {
  self.prompt({
    type: 'confirm',
    name: 'doClear',
    message: chalk.bold.red('Are you certain you\'d like to purge locally saved addresses and encrypted private keys?'),
    default: false
  }, (result) => {
    if (result.doClear) {
      conf.set('addresses', [])
      self.log(chalk.white(' Wallet cleared.'))
    }
    cb()
  })
}

function encryptKey(self, newWif, inputs) {
  let deferred = q.defer();

  neonjs.encryptWifAccount(newWif, inputs.passphrase.toString())
    .then((newAccount) => {
      let wallet = {
        label: (inputs.label != undefined ? inputs.label : ''),
        address: newAccount.address,
        encryptedWif: newAccount.encryptedWif
      }
      let addresses = conf.get('addresses')
      addresses.push(wallet)
      conf.set('addresses', addresses);
      self.log(chalk.bold.green(' This information will only be displayed once!'))
      self.log(chalk.green(' Your address and encrypted private key will be stored locally on your machine.'))
      self.log(chalk.green(' Your private key and passphrase will not be stored anywhere.'))
      self.log(chalk.green(' Label: ') + chalk.bold.green(inputs.label))
      self.log(chalk.green(' Public Address: ') + chalk.bold.green(newAccount.address))
      self.log(chalk.green(' Private Key: ') + chalk.bold.green(newAccount.wif))
      self.log(chalk.green(' Encrypted Key: ') + chalk.bold.green(newAccount.encryptedWif))
      self.log(chalk.green(' Passphrase: ') + chalk.bold.green(newAccount.passphrase))
      self.log(chalk.bold.red(' Now is your only chance to make a secure copy of your private key!'))
      self.prompt({
        type: 'confirm',
        name: 'keysaved',
        message: chalk.green('Have you saved your private key securely? '),
        default: true
      }, function (answer) {
        self.log(chalk.green(' I must believe you have.'))
        deferred.resolve(answer)
      })
    })

  return deferred.promise;
}

function walletShow(self, args, cb) {
  let addresses = conf.get('addresses')
  let addressList = []
  let addressIndex
  if (addresses.length <= 0) {
    self.log(' No addresses found.')
    cb()
  } else {
    for (let i = 0; i < addresses.length; i++) {
      addressList.push((addresses[i].label ? addresses[i].label + ' (' : '') + addresses[i].address + (addresses[i].label ? ')' : ''))
    }
    self.prompt({
      type: 'list',
      name: 'addressSelection',
      message: chalk.green('Which address would you like to see?'),
      choices: addressList
    }, (result) => {
      let address = result.addressSelection;
      if (address.indexOf('(') >= 0) {
        let foundAddress = address.match(/\((.*)\)/)
        address = foundAddress[1]
      }
      for (let i = 0; i < addresses.length; i++) {
        if (addresses[i].address == address) {
          addressIndex = i
        }
      }
      let tableData = []
      for (let asset in addresses[addressIndex].balances) {
        if (asset != 'unspent') {
          let row = [
            chalk.green(asset),
            chalk.green(addresses[addressIndex].balances[asset])
          ]
          tableData.push(row)
        }
      }
      self.log(chalk.bold.green('Balances'))
      self.log(asTable(tableData).toString())

      let transactionHistory = neonjs.getTransactionHistory(network.get(), address)
        .then((txns) => {
          self.log('')
          if (txns.length == 0) {
            self.log(chalk.green(' No transactions found.'))
          } else {
            let txnData = []
            let txnQty = (txns.length <= 20 ? txns.length : 20)
            for (let i = 0; i < txnQty; i++) {
              let row = {
                Asset: chalk.green(txns[i].neo_sent ? 'Neo' : 'Gas'),
                Amount: chalk.green(txns[i][(txns[i].neo_sent ? 'NEO' : 'GAS')]),
                TransacionID: chalk.dim.green(txns[i].txid)
              }
              txnData.push(row)
            }
            self.log(asTable(txnData).toString())
            self.log(chalk.dim.green(txnQty + ' Most recent transactions'))
          }
        })
        .then(() => {
          let claimAmounts = neonjs.getClaimAmounts(network.get(), address)
            .then((claimables) => {
              self.log('')
              let gasToClaim = (claimables.available + claimables.unavailable) / 100000000
              self.log(chalk.bold.green('Claimable gas: ') + chalk.green(gasToClaim))
              cb()
            })
        })
    })
  }
}

function walletUpdateBalances(self) {
  let addresses = conf.get('addresses')
  for (let i = 0; i < addresses.length; i++) {
    neonjs.getBalance(network.get(), addresses[i].address)
      .then((result) => {
        addresses[i].balances = result
        conf.set('addresses', addresses)
      })
  }
}

module.exports.create = walletCreate
module.exports.import = walletImport
module.exports.list = walletList
module.exports.remove = walletRemove
module.exports.clear = walletClear
module.exports.show = walletShow
module.exports.updateBalances = walletUpdateBalances
<|endoftext|>