<commit_before>var mongoose = require("mongoose")
  , _ = require("underscore")
  , async = require("async")
  , Status = require("./status")

/**
 * The schema for an embedded friendship
 */
var Friendship = new mongoose.Schema({
  // the status of this friendship, see the Status module for details
  status: {type: String, enum: _.values(Status)},

  // when this friendship was added
  added: Date
})

/**
 * Default option values
 */
var optionDefaults = {
  /**
   * The name of the document array "friends" path (default: "friends")
   */
  pathName: "friends",

  /**
   * Should an index be added ot the friends doc array? (default: true)
   */
  index: true
};

/**
 * expose the plugin function as exports
 *
 * Options
 * ---
 * - {String} pathName the pathname for the friends document array (default "friends")
 * - {Boolean} index whether the plugin should automatically index the friends array (default: true)
 *
 * @param {Object} options
 */
module.exports = plugin;

/**
 * expose Status as a property of the plugin
 */
module.exports.Status = Status;

function plugin (options) {
  options = _.defaults(options || {}, optionDefaults);

  // the pathName for the friends array
  var pathName = options.pathName;

  // Fields to add to the extending model
  var fields = {};
  fields[pathName] = { type: [Friendship], select: false };

  /**
   * The work function which pushes or updates embedded friend objects
   * for two documents, returns a function
   *
   * @api private
   */
  var _update = function (query, update, fship) {
    var options = { new: false };

    return function (done) {
      this.findOneAndUpdate(query, update, options, function (err, res) {
        done(err, fship);
      });
    }
  }

  /**
   * Return a function to update a friendship between two parties
   *
   * @api private
   */
  var updateFriendship = function (m1, m2, fship) {
    var query = {_id: m1};
    query[pathName] = {$elemMatch: {_id: m2}};

    var updater = {$set: {}};
    updater.$set[pathName+".$.status"] = fship.status;

    return _update(query, updater, fship);
  }

  /**
   * Return a function to create a new friendship between two parties
   *
   * @api private
   */
  var pushFriendship = function (m1, m2, fship) {
    var query = {_id: m1};

    fship.added = new Date();

    var updater = {$push: {}};
    updater.$push[pathName] = fship;

    return _update(query, updater, fship);
  }

  /**
   * Generate a function to return one side of a friendship between
   * two models
   *
   * @param Model the extending model
   * @param m1 the model or model _id being queried
   * @param m2 the model or model _id whose friendship is queried for
   *
   * @returns a function which will call back with an friendship for m2 on m1
   * @api private
   */
  var friendshipBetween = function (m1, m2) {
    var proj = {};
    proj[pathName] = {$elemMatch: {_id: m2}};

    return function (done) {
      this.findById(m1, proj, function (err, doc) {
        if (err) return done(err);
        if (!doc) return done("friendship not found");
        done(null, doc[pathName][0]);
      });
    }
  }

  return function friends (schema) {
    // add the embedded friends
    schema.add(fields);

    // Index the friends array with a multikey index on _id.  Further indexing
    // on status is probably unnecessary, as all queries will hit _id and
    // this will already limit them to the friends on an individual user.
    if (options.index) {
      var index = {};
      index[pathName+"._id"] = 1;
      schema.index(index, {name: "friendsplugin"});
    }

    /**
     * Send friend request from "friender" to "friend".  Calls back with
     * an object containing the two resulting friend objects
     *
     * On first request, will result in a "requested" friendship for the
     * first party and a "pending" friendship for the second.
     *
     * Reciprocating the request (friendee back to friender) will accept it.
     *
     * @param {Model} m1 the "friender" model doc or _id doing the reqesting
     * @param {Model} m2 the "friendee" model doc or _id being requested
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.requestFriend = function (m1, m2, cb) {
      var steps = {}
        , Model = this

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      async.auto({
        m1: friendshipBetween(m1, m2).bind(this),
        m2: friendshipBetween(m2, m1).bind(this)
      }, function (err, o) {
        if (err) return cb(err);

        var hasfship = !!o.m1
          , fship = o.m1 || {_id: m2}
          , oid = fship._id
          , ostatus = fship.status

        // m2 has no friendship, add a new pending friendship, mark
        // m1's status as requested
        if (!o.m2) {
          fship.status = Status.Requested;
          steps.friend = pushFriendship(m2, m1, {
            _id: m1,
            status: Status.Pending
          }).bind(Model);
        } else {
          switch (o.m2.status) {

          // m2 status is still pending, no update
          case Status.Pending:
            fship.status = Status.Requested;
            break;

          // m2 status is accepted already, no update
          case Status.Accepted:
            fship.status = Status.Accepted;
            break;

          // m2 already requested m1, mark BOTH friendships as accepted
          case Status.Requested:
            fship.status = Status.Accepted;
            steps.friend = updateFriendship(m2, m1, {
              status: Status.Accepted
            }).bind(Model);
            break;
          }
        }

        steps.friend || (steps.friend = function (done) {
          // If no update was necessary, send the friendship back directly
          done(null, o.m2)
        });

        if (hasfship && ostatus == fship.status && oid.equals(fship._id)) {
          steps.friender = function (done) {
            done(null, fship)
          }
        } else if (hasfship) {
          steps.friender = updateFriendship(m1, m2, fship, cb).bind(Model)
        } else {
          steps.friender = pushFriendship(m1, m2, fship, cb).bind(Model)
        }

        async.parallel(steps, cb);
      });
    };

    /**
     * Create a friend request
     *
     * @param {Model} friend The potential friend being requested
     * @param {Function} cb Callback once friendship is created
     */
    schema.methods.requestFriend = function (friend, cb) {
      return this.constructor.requestFriend(this, friend, cb);
    };

    /**
     * Get all friends of a model
     *
     * Options
     * ---
     * - {String} status optional status to filter by, one of pending|accepted|requested
     * - {String|Object} select the field select to pass along to mongoose
     *
     * @param {Model} model a model doc or _id of a model doc to find friends for
     * @param {Object} options
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.getFriends = function (model) {
      var Model = this
        , op = 'find'

      resolveArgs(arguments, function (conditions, fields, options, cb) {
        // assume model is either a Model, or an _id
        model = model._id || model;

        conditions[pathName+"._id"] = model;

        // "accepted" status is mirrored on both sides, but if querying
        // for "pending" or "requested" friends, the query must be reversed
        if (conditions[pathName+".status"]) {
          switch (conditions[pathName+".status"]) {
          case Status.Pending:
            conditions[pathName+".status"] = Status.Requested;
            break;
          case Status.Requested:
            conditions[pathName+".status"] = Status.Pending;
            break;
          }
        }

        async.parallel({
          // query remote friends based on the arguments
          friends: function (done) {
            Model[op](conditions, fields, options, done);
          },

          // Reduce local friend docs to map which will be populated and
          // then combined with the queried remote friends to generate the
          // resulting array
          locals: function (done) {
            var select = {};
            select[pathName] = 1;

            Model.findOne({_id: model}, select, function (err, doc) {
              if (err) return done(err);
              if (!doc) return done(null, []);

              done(null, _.reduce(doc[pathName], function (o, friend) {
                o[friend._id] = friend.toObject();
                return o;
              }, {}));
            });
          }
        }, function (err, res) {
          if (err) return cb(err);

          // wrap the results with the status
          for (var i=0, friendship; i<res.friends.length; i++) {
            friendship = res.locals[res.friends[i]._id];
            friendship.friend = res.friends[i];
            res.friends[i] = friendship;
          }

          cb(null, res.friends);
        });
      });
    };

    /**
     * Get all friends of this model
     *
     * Options
     * ---
     * - {String} status optional status to filter by, one of pending|accepted|requested
     * - {String|Object} select the field select to pass along to mongoose
     *
     * @param {Object} options
     * @param {Function} cb callback to execute on completion or error
     */
    schema.methods.getFriends = function (conditions, fields, options, cb) {
      return this.constructor.getFriends(this, conditions, fields, options, cb);
    };

    var getByStatus = function (status) {
      return function (model) {
        resolveArgs(arguments, function (conditions, fields, options, cb) {
          conditions[pathName + ".status"] = status;
          this.getFriends(model, conditions, fields, options, cb);
        }.bind(this));
      }
    }

    /**
     *
     */
    schema.statics.getPendingFriends = getByStatus(Status.Pending);

    /**
     *
     */
    schema.methods.getPendingFriends = function (conditions, fields, options, cb) {
      return this.constructor.getPendingFriends(this, conditions, fields, options, cb);
    }

    /**
     *
     */
    schema.statics.getAcceptedFriends = getByStatus(Status.Accepted);

    /**
     *
     */
    schema.methods.getAcceptedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getAcceptedFriends(this, conditions, fields, options, cb);
    }

    /**
     *
     */
    schema.statics.getRequestedFriends = getByStatus(Status.Requested);

    /**
     *
     */
    schema.methods.getRequestedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getRequestedFriends(this, conditions, fields, options, cb);
    }

    /**
     * Remove a friendship between two friends
     *
     * @param {Model} m1 the first model doc
     * @param {Model} m2 the second model doc
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.removeFriend = function (m1, m2, cb) {
      var collection = this.collection

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      var pull = function (m1, m2) {
        var update = {$pull: {}}
        update.$pull[pathName] = {_id: m2};
        return function (done) {
          collection.update({_id: m1}, update, done);
        }
      }

      async.parallel([pull(m1, m2), pull(m2, m1)], cb);
    };

    /**
     * Remove a friend of this model
     *
     * @param {Model} friend doc to remove
     * @param {Function} cb callback to execute on completion or error
     */
    schema.methods.removeFriend = function (model, cb) {
      return this.constructor.removeFriend(this, model, cb);
    };
  };
}

/**
 * Expects a signature like Mongoose's `Model.find`, but for the first
 * arg.  Goes through the argument same disambigation that `Model.find`
 * does then calls a callback with the results.  This keeps all this
 * business in one place and lets other functions hook into the options
 * of `getFriends` without copy/pasting this Mongoose logic.
 *
 * @api private
 */
function resolveArgs (args, next) {
  var conditions = args[1]
    , fields = args[2]
    , options = args[3]
    , callback = args[4]

  if ('function' == typeof conditions) {
    callback = conditions;
    conditions = {};
    fields = null;
    options = null;
  } else if ('function' == typeof fields) {
    callback = fields;
    fields = null;
    options = null;
  } else if ('function' == typeof options) {
    callback = options;
    options = null;
  }
  next(conditions, fields, options, callback);
}

/**
 * simple no op function for clarity
 * @api private
 */
function noop() {}
<commit_msg>fix bug embedding nested Schema vs an object (latter allowed in newest mongoose only)<commit_after>var mongoose = require("mongoose")
  , _ = require("underscore")
  , async = require("async")
  , Status = require("./status")

/**
 * The schema for an embedded friendship.  (Treated as an object for compat
 * with embedding schemas)
 */
var Friendship = {
  // the status of this friendship, see the Status module for details
  status: {type: String, enum: _.values(Status)},

  // when this friendship was added
  added: Date
}

/**
 * Default option values
 */
var optionDefaults = {
  /**
   * The name of the document array "friends" path (default: "friends")
   */
  pathName: "friends",

  /**
   * Should an index be added ot the friends doc array? (default: true)
   */
  index: true
};

/**
 * expose the plugin function as exports
 *
 * Options
 * ---
 * - {String} pathName the pathname for the friends document array (default "friends")
 * - {Boolean} index whether the plugin should automatically index the friends array (default: true)
 *
 * @param {Object} options
 */
module.exports = plugin;

/**
 * expose Status as a property of the plugin
 */
module.exports.Status = Status;

function plugin (options) {
  options = _.defaults(options || {}, optionDefaults);

  // the pathName for the friends array
  var pathName = options.pathName;

  // Fields to add to the extending model
  var fields = {};
  fields[pathName] = { type: [Friendship], select: false };

  /**
   * The work function which pushes or updates embedded friend objects
   * for two documents, returns a function
   *
   * @api private
   */
  var _update = function (query, update, fship) {
    var options = { new: false };

    return function (done) {
      this.findOneAndUpdate(query, update, options, function (err, res) {
        done(err, fship);
      });
    }
  }

  /**
   * Return a function to update a friendship between two parties
   *
   * @api private
   */
  var updateFriendship = function (m1, m2, fship) {
    var query = {_id: m1};
    query[pathName] = {$elemMatch: {_id: m2}};

    var updater = {$set: {}};
    updater.$set[pathName+".$.status"] = fship.status;

    return _update(query, updater, fship);
  }

  /**
   * Return a function to create a new friendship between two parties
   *
   * @api private
   */
  var pushFriendship = function (m1, m2, fship) {
    var query = {_id: m1};

    fship.added = new Date();

    var updater = {$push: {}};
    updater.$push[pathName] = fship;

    return _update(query, updater, fship);
  }

  /**
   * Generate a function to return one side of a friendship between
   * two models
   *
   * @param Model the extending model
   * @param m1 the model or model _id being queried
   * @param m2 the model or model _id whose friendship is queried for
   *
   * @returns a function which will call back with an friendship for m2 on m1
   * @api private
   */
  var friendshipBetween = function (m1, m2) {
    var proj = {};
    proj[pathName] = {$elemMatch: {_id: m2}};

    return function (done) {
      this.findById(m1, proj, function (err, doc) {
        if (err) return done(err);
        if (!doc) return done("friendship not found");
        done(null, doc[pathName][0]);
      });
    }
  }

  return function friends (schema) {
    // add the embedded friends
    schema.add(fields);

    // Index the friends array with a multikey index on _id.  Further indexing
    // on status is probably unnecessary, as all queries will hit _id and
    // this will already limit them to the friends on an individual user.
    if (options.index) {
      var index = {};
      index[pathName+"._id"] = 1;
      schema.index(index, {name: "friendsplugin"});
    }

    /**
     * Send friend request from "friender" to "friend".  Calls back with
     * an object containing the two resulting friend objects
     *
     * On first request, will result in a "requested" friendship for the
     * first party and a "pending" friendship for the second.
     *
     * Reciprocating the request (friendee back to friender) will accept it.
     *
     * @param {Model} m1 the "friender" model doc or _id doing the reqesting
     * @param {Model} m2 the "friendee" model doc or _id being requested
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.requestFriend = function (m1, m2, cb) {
      var steps = {}
        , Model = this

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      async.auto({
        m1: friendshipBetween(m1, m2).bind(this),
        m2: friendshipBetween(m2, m1).bind(this)
      }, function (err, o) {
        if (err) return cb(err);

        var hasfship = !!o.m1
          , fship = o.m1 || {_id: m2}
          , oid = fship._id
          , ostatus = fship.status

        // m2 has no friendship, add a new pending friendship, mark
        // m1's status as requested
        if (!o.m2) {
          fship.status = Status.Requested;
          steps.friend = pushFriendship(m2, m1, {
            _id: m1,
            status: Status.Pending
          }).bind(Model);
        } else {
          switch (o.m2.status) {

          // m2 status is still pending, no update
          case Status.Pending:
            fship.status = Status.Requested;
            break;

          // m2 status is accepted already, no update
          case Status.Accepted:
            fship.status = Status.Accepted;
            break;

          // m2 already requested m1, mark BOTH friendships as accepted
          case Status.Requested:
            fship.status = Status.Accepted;
            steps.friend = updateFriendship(m2, m1, {
              status: Status.Accepted
            }).bind(Model);
            break;
          }
        }

        steps.friend || (steps.friend = function (done) {
          // If no update was necessary, send the friendship back directly
          done(null, o.m2)
        });

        if (hasfship && ostatus == fship.status && oid.equals(fship._id)) {
          steps.friender = function (done) {
            done(null, fship)
          }
        } else if (hasfship) {
          steps.friender = updateFriendship(m1, m2, fship, cb).bind(Model)
        } else {
          steps.friender = pushFriendship(m1, m2, fship, cb).bind(Model)
        }

        async.parallel(steps, cb);
      });
    };

    /**
     * Create a friend request
     *
     * @param {Model} friend The potential friend being requested
     * @param {Function} cb Callback once friendship is created
     */
    schema.methods.requestFriend = function (friend, cb) {
      return this.constructor.requestFriend(this, friend, cb);
    };

    /**
     * Get all friends of a model
     *
     * Options
     * ---
     * - {String} status optional status to filter by, one of pending|accepted|requested
     * - {String|Object} select the field select to pass along to mongoose
     *
     * @param {Model} model a model doc or _id of a model doc to find friends for
     * @param {Object} options
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.getFriends = function (model) {
      var Model = this
        , op = 'find'

      resolveArgs(arguments, function (conditions, fields, options, cb) {
        // assume model is either a Model, or an _id
        model = model._id || model;

        conditions[pathName+"._id"] = model;

        // "accepted" status is mirrored on both sides, but if querying
        // for "pending" or "requested" friends, the query must be reversed
        if (conditions[pathName+".status"]) {
          switch (conditions[pathName+".status"]) {
          case Status.Pending:
            conditions[pathName+".status"] = Status.Requested;
            break;
          case Status.Requested:
            conditions[pathName+".status"] = Status.Pending;
            break;
          }
        }

        async.parallel({
          // query remote friends based on the arguments
          friends: function (done) {
            Model[op](conditions, fields, options, done);
          },

          // Reduce local friend docs to map which will be populated and
          // then combined with the queried remote friends to generate the
          // resulting array
          locals: function (done) {
            var select = {};
            select[pathName] = 1;

            Model.findOne({_id: model}, select, function (err, doc) {
              if (err) return done(err);
              if (!doc) return done(null, []);

              done(null, _.reduce(doc[pathName], function (o, friend) {
                o[friend._id] = friend.toObject();
                return o;
              }, {}));
            });
          }
        }, function (err, res) {
          if (err) return cb(err);

          // wrap the results with the status
          for (var i=0, friendship; i<res.friends.length; i++) {
            friendship = res.locals[res.friends[i]._id];
            friendship.friend = res.friends[i];
            res.friends[i] = friendship;
          }

          cb(null, res.friends);
        });
      });
    };

    /**
     * Get all friends of this model
     *
     * Options
     * ---
     * - {String} status optional status to filter by, one of pending|accepted|requested
     * - {String|Object} select the field select to pass along to mongoose
     *
     * @param {Object} options
     * @param {Function} cb callback to execute on completion or error
     */
    schema.methods.getFriends = function (conditions, fields, options, cb) {
      return this.constructor.getFriends(this, conditions, fields, options, cb);
    };

    var getByStatus = function (status) {
      return function (model) {
        resolveArgs(arguments, function (conditions, fields, options, cb) {
          conditions[pathName + ".status"] = status;
          this.getFriends(model, conditions, fields, options, cb);
        }.bind(this));
      }
    }

    /**
     *
     */
    schema.statics.getPendingFriends = getByStatus(Status.Pending);

    /**
     *
     */
    schema.methods.getPendingFriends = function (conditions, fields, options, cb) {
      return this.constructor.getPendingFriends(this, conditions, fields, options, cb);
    }

    /**
     *
     */
    schema.statics.getAcceptedFriends = getByStatus(Status.Accepted);

    /**
     *
     */
    schema.methods.getAcceptedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getAcceptedFriends(this, conditions, fields, options, cb);
    }

    /**
     *
     */
    schema.statics.getRequestedFriends = getByStatus(Status.Requested);

    /**
     *
     */
    schema.methods.getRequestedFriends = function (conditions, fields, options, cb) {
      return this.constructor.getRequestedFriends(this, conditions, fields, options, cb);
    }

    /**
     * Remove a friendship between two friends
     *
     * @param {Model} m1 the first model doc
     * @param {Model} m2 the second model doc
     * @param {Function} cb callback to execute on completion or error
     */
    schema.statics.removeFriend = function (m1, m2, cb) {
      var collection = this.collection

      m1 = m1._id || m1;
      m2 = m2._id || m2;

      cb || (cb = noop);

      var pull = function (m1, m2) {
        var update = {$pull: {}}
        update.$pull[pathName] = {_id: m2};
        return function (done) {
          collection.update({_id: m1}, update, done);
        }
      }

      async.parallel([pull(m1, m2), pull(m2, m1)], cb);
    };

    /**
     * Remove a friend of this model
     *
     * @param {Model} friend doc to remove
     * @param {Function} cb callback to execute on completion or error
     */
    schema.methods.removeFriend = function (model, cb) {
      return this.constructor.removeFriend(this, model, cb);
    };
  };
}

/**
 * Expects a signature like Mongoose's `Model.find`, but for the first
 * arg.  Goes through the argument same disambigation that `Model.find`
 * does then calls a callback with the results.  This keeps all this
 * business in one place and lets other functions hook into the options
 * of `getFriends` without copy/pasting this Mongoose logic.
 *
 * @api private
 */
function resolveArgs (args, next) {
  var conditions = args[1]
    , fields = args[2]
    , options = args[3]
    , callback = args[4]

  if ('function' == typeof conditions) {
    callback = conditions;
    conditions = {};
    fields = null;
    options = null;
  } else if ('function' == typeof fields) {
    callback = fields;
    fields = null;
    options = null;
  } else if ('function' == typeof options) {
    callback = options;
    options = null;
  }
  next(conditions, fields, options, callback);
}

/**
 * simple no op function for clarity
 * @api private
 */
function noop() {}
<|endoftext|>


<commit_before>'use strict';

var EventEmitter = require('eventemitter3')
  , slice = Array.prototype.slice
  , iframe = require('./iframe')
  , Image = require('./image');

/**
 * Representation of a single container.
 *
 * Options:
 *
 * - retries; When an error occurs, how many times should we attempt to restart
 *   the code before we automatically stop() the container.
 * - stop; Stop the container when an error occurs.
 * - timeout; How long can a ping packet timeout before we assume that the
 *   container has died and should be restarted.
 *
 * @constructor
 * @param {Element} mount The element we should attach to.
 * @param {String} id A unique id for this container.
 * @param {String} code The actual that needs to run within the sandbox.
 * @param {Object} options Container configuration.
 * @api private
 */
function Container(mount, id, code, options) {
  options = options || {};

  this.i = iframe(mount, id);         // The generated iframe.
  this.mount = mount;                 // Mount point of the container.
  this.console = [];                  // Historic console.* output.
  this.id = id;                       // Unique id.
  this.readyState = Container.CLOSED; // The readyState of the container.

  this.created = +new Date();         // Creation EPOCH.
  this.started = null;                // Start EPOCH.

  this.retries = 'retries' in options // How many times should we reload
    ? options.reties
    : 3;

  this.timeout = 'timeout' in options //
    ? +options.timeout
    : 1000;

  //
  // Initialise as an EventEmitter before we start loading in the code.
  //
  EventEmitter.call(this);

  //
  // Optional code to load in the container and start it directly.
  //
  if (code) this.load(code).start();
}

//
// The container inherits from the EventEmitter3.
//
Container.prototype = new EventEmitter();

/**
 * Internal readyStates for the container.
 *
 * @type {Number}
 * @private
 */
Container.CLOSING = 1;
Container.OPENING = 2;
Container.CLOSED  = 3;
Container.OPEN    = 4;

/**
 * Start a new ping timeout.
 *
 * @api private
 */
Container.prototype.ping = function ping() {
  if (this.pong) clearTimeout(this.pong);

  this.pong = setTimeout(this.bound(function pong() {
    this.onmessage({ type: "error", scope: "iframe.timeout", args: [
      'the iframe is no longer responding with ping packets'
    ] });
  }), this.timeout);

  return this;
};

/**
 * Retry loading the code in the iframe. The container will be restored to a new
 * state or
 */
Container.prototype.retry = function retry() {
  switch (this.retries) {
    //
    // This is our last attempt, we've tried to have the iframe restart the code
    // it self, so for our last attempt we're going to completely create a new
    // iframe and re-compile the code for it.
    //
    case 1:
      this.stop(); // Clear old iframe and nuke it's references
      this.i = iframe(this.mount, this.id);
      this.load(this.image.source).start();
    break;

    //
    // No more attempts left.
    //
    case 0:
      this.emit('end');
    return;

    //
    // By starting and stopping (and there for removing and adding it back to
    // the DOM) the iframe will reload it's HTML and the added code.
    //
    default:
      this.stop().start();
    break;
  }

  this.emit('retry', this.retries);
  this.retries--;

  return this;
};

/**
 * Inspect the container to get some useful statistics about it and it's health.
 *
 * @returns {Object}
 * @api public
 */
Container.prototype.inspect = function inspect() {
  var memory;

  //
  // Try to read out the `performance` information from the iframe.
  //
  if (this.i.window && this.i.window.performance) {
    memory = this.i.window.performance.memory;
  }

  memory = memory || {};

  return {
    state: this.readyState,
    uptime: +new Date() - this.started,
    attached: !!document.getElementById(this.id),
    memory: {
      limit: memory.jsHeapSizeLimit || 0,
      total: memory.totalJSHeapSize || 0,
      used: memory.usedJSHeapSize || 0
    }
  };
};

/**
 * Bind, without the .bind. This ensures that callbacks and functions are called
 * with the correct context.
 *
 * @param {Function} method The method that we should bind to.
 * @param {Mixed} context The context of the method, default to `this`
 * @returns {Function} Function that calls the method with the given context.
 * @api private
 */
Container.prototype.bound = function bound(method, context) {
  method = method || function noop() {};  // default to noop.
  context = context || this;              // default to `this`.

  var args = slice.call(arguments, 2);

  return function binded() {
    return method.apply(context, args.concat(slice.call(arguments, 0)));
  };
};

/**
 * Parse and process incoming messages from the iframe. The incoming messages
 * should be objects that have a `type` property. The main reason why we have
 * this as a separate method is to give us flexiblity. We are leveraging iframes
 * at the moment, but in the future we might want to leverage WebWorkers for the
 * sandboxing of JavaScript.
 *
 * @param {Object} packet The incoming message.
 * @returns {Boolean} Message was handled y/n.
 * @api private
 */
Container.prototype.onmessage = function onmessage(packet) {
  if ('object' !== typeof packet) return false;

  switch (packet.type) {
    //
    // The code in the iframe used the `console` method.
    //
    case 'console':
      this.console.push({
        method: packet.method,
        epoch: +new Date(),
        args: packet.args
      });

      if (packet.attach) {
        this.emit.apply(this, ['attach::'+ packet.method].conat(packet.args));
        this.emit.apply(this, ['attach'].conat(packet.args));
      }
    break;

    //
    // An error happend in the iframe, process it.
    //
    case 'error':
      this.emit('error', new Error(packet.args[0])).retry();
    break;

    //
    // We've received a ping response from the iframe, so we know it's still
    // running as intended.
    //
    case 'ping':
      this.ping();
    break;

    //
    // Handle unknown package types by just returning false.
    //
    default:
      return false;
  }

  return true;
};

/**
 * Small wrapper around sandbox evaluation.
 *
 * @param {String} cmd The command to executed in the iframe.
 * @param {Function} fn Callback
 * @api public
 */
Container.prototype.eval = function evil(cmd, fn) {
  try {
    this.i.window.eval(cmd);
  } catch (e) {
    return fn(e);
  }

  return fn();
};

/**
 * Error handling.
 *
 * @returns {Boolean}
 * @api private
 */
Container.prototype.onerror = function onerror() {
  var a = slice.call(arguments, 0);
  this.onmessage({ type: "error", scope: "iframe.onerror", args: a });

  return true;
};

/**
 * The container has been fully loaded and the iframe has executed it's code.
 *
 * @returns {Boolean}
 * @api private
 */
Container.prototype.onload = function onload() {
  this.readyState = Container.OPEN;
  this.emit('start');

  return true;
};

Container.prototype.onunload = function onunload() {
  this.readyState = Container.CLOSED;
  this.emit('stop');

  return true;
};

/**
 * Start the container.
 *
 * @returns {Container}
 * @api public
 */
Container.prototype.start = function start() {
  this.readyState = Container.OPENING;

  //
  // Attach various event listeners so we can update the state of the container.
  // We don't need to use `.addEventLister` as we only want and require one
  // single event listener.
  //
  this.i.window.onerror = this.bound(this.onerror);
  this.i.frame.onload = this.bound(this.onload);
  this.i.frame.onunload = this.bound(this.unload);

  //
  // If the container is already in the HTML we're going to assume that we still
  // have to load it with the Image. But if it's not in the mount point (DOM) we
  // assume that the iframe has been removed to release memory and what ever,
  // but when we re-add it to the mount point, it will automatically restart the
  // JavaScript that was originally loaded in the container.
  //
  if (!document.getElementById(this.id)) {
    this.mount.appendChild(this.i.frame);
  } else {
    var doc = this.i.document;

    doc.open();
    doc.write('<!doctype html><s'+'cript>'+ this.image +'</s'+'cript>');
    doc.close();
  }

  //
  // Introduce a custom variable in to the iframe that exposes our Container as
  // API. The Image can use this to communicate with the container and pass
  // messages back and forth.
  //
  this.i.window[this.id] = this.bound(this.onmessage);
  this.started = +new Date();

  return this;
};

/**
 * Stop running the code inside the container.
 *
 * @returns {Container}
 * @api private
 */
Container.prototype.stop = function stop() {
  if (!document.getElementById(this.id)) return this;

  this.readyState = Container.CLOSING;
  this.i.window[this.id] = null;
  this.mount.removeChild(this.i.frame);

  //
  // Known to throw errors in certain situations (IE)
  //
  try { this.i.frame.onload = null; }
  catch (e) {}

  try { this.i.frame.onunload = null; }
  catch (e) {}

  try { this.i.window.onerror = null; }
  catch (e) {}

  return this;
};

/**
 * Load the given code as image on to the container.
 *
 * @param {String} code The code that should run on the container.
 * @returns {Container}
 * @api public
 */
Container.prototype.load = function load(code) {
  this.image = new Image(this.id, code);

  return this;
};

/**
 * Completely destroy the given container and ensure that all references are
 * nuked so we can clean up as much memory as possible.
 *
 * @returns {Container}
 * @api private
 */
Container.prototype.destroy = function destroy() {
  this.stop();

  //
  // Remove all possible references to release as much memory as possible.
  //
  this.mount = this.image = this.id = this.i = this.created = null;
  this.removeAllListeners();

  return this;
};

//
// Expose the module.
//
module.exports = Container;
<commit_msg>[minor] Work on an API for iframe > container communication<commit_after>'use strict';

var EventEmitter = require('eventemitter3')
  , slice = Array.prototype.slice
  , iframe = require('./iframe')
  , Image = require('./image');

/**
 * Representation of a single container.
 *
 * Options:
 *
 * - retries; When an error occurs, how many times should we attempt to restart
 *   the code before we automatically stop() the container.
 * - stop; Stop the container when an error occurs.
 * - timeout; How long can a ping packet timeout before we assume that the
 *   container has died and should be restarted.
 *
 * @constructor
 * @param {Element} mount The element we should attach to.
 * @param {String} id A unique id for this container.
 * @param {String} code The actual that needs to run within the sandbox.
 * @param {Object} options Container configuration.
 * @api private
 */
function Container(mount, id, code, options) {
  options = options || {};

  this.i = iframe(mount, id);         // The generated iframe.
  this.mount = mount;                 // Mount point of the container.
  this.console = [];                  // Historic console.* output.
  this.id = id;                       // Unique id.
  this.readyState = Container.CLOSED; // The readyState of the container.

  this.created = +new Date();         // Creation EPOCH.
  this.started = null;                // Start EPOCH.

  this.retries = 'retries' in options // How many times should we reload
    ? options.reties
    : 3;

  this.timeout = 'timeout' in options //
    ? +options.timeout
    : 1000;

  //
  // Initialise as an EventEmitter before we start loading in the code.
  //
  EventEmitter.call(this);

  //
  // Optional code to load in the container and start it directly.
  //
  if (code) this.load(code).start();
}

//
// The container inherits from the EventEmitter3.
//
Container.prototype = new EventEmitter();

/**
 * Internal readyStates for the container.
 *
 * @type {Number}
 * @private
 */
Container.CLOSING = 1;
Container.OPENING = 2;
Container.CLOSED  = 3;
Container.OPEN    = 4;

/**
 * Start a new ping timeout.
 *
 * @api private
 */
Container.prototype.ping = function ping() {
  if (this.pong) clearTimeout(this.pong);

  this.pong = setTimeout(this.bound(function pong() {
    this.onmessage({ type: "error", scope: "iframe.timeout", args: [
      'the iframe is no longer responding with ping packets'
    ] });
  }), this.timeout);

  return this;
};

/**
 * Retry loading the code in the iframe. The container will be restored to a new
 * state or completely reset the iframe.
 *
 * @api private
 */
Container.prototype.retry = function retry() {
  switch (this.retries) {
    //
    // This is our last attempt, we've tried to have the iframe restart the code
    // it self, so for our last attempt we're going to completely create a new
    // iframe and re-compile the code for it.
    //
    case 1:
      this.stop(); // Clear old iframe and nuke it's references
      this.i = iframe(this.mount, this.id);
      this.load(this.image.source).start();
    break;

    //
    // No more attempts left.
    //
    case 0:
      this.emit('end');
    return;

    //
    // By starting and stopping (and there for removing and adding it back to
    // the DOM) the iframe will reload it's HTML and the added code.
    //
    default:
      this.stop().start();
    break;
  }

  this.emit('retry', this.retries);
  this.retries--;

  return this;
};

/**
 * Inspect the container to get some useful statistics about it and it's health.
 *
 * @returns {Object}
 * @api public
 */
Container.prototype.inspect = function inspect() {
  var memory;

  //
  // Try to read out the `performance` information from the iframe.
  //
  if (this.i.window && this.i.window.performance) {
    memory = this.i.window.performance.memory;
  }

  memory = memory || {};

  return {
    state: this.readyState,
    uptime: +new Date() - this.started,
    attached: !!document.getElementById(this.id),
    memory: {
      limit: memory.jsHeapSizeLimit || 0,
      total: memory.totalJSHeapSize || 0,
      used: memory.usedJSHeapSize || 0
    }
  };
};

/**
 * Bind, without the .bind. This ensures that callbacks and functions are called
 * with the correct context.
 *
 * @param {Function} method The method that we should bind to.
 * @param {Mixed} context The context of the method, default to `this`
 * @returns {Function} Function that calls the method with the given context.
 * @api private
 */
Container.prototype.bound = function bound(method, context) {
  method = method || function noop() {};  // default to noop.
  context = context || this;              // default to `this`.

  var args = slice.call(arguments, 2);

  return function binded() {
    return method.apply(context, args.concat(slice.call(arguments, 0)));
  };
};

/**
 * Parse and process incoming messages from the iframe. The incoming messages
 * should be objects that have a `type` property. The main reason why we have
 * this as a separate method is to give us flexiblity. We are leveraging iframes
 * at the moment, but in the future we might want to leverage WebWorkers for the
 * sandboxing of JavaScript.
 *
 * @param {Object} packet The incoming message.
 * @returns {Boolean} Message was handled y/n.
 * @api private
 */
Container.prototype.onmessage = function onmessage(packet) {
  if ('object' !== typeof packet) return false;

  switch (packet.type) {
    //
    // The code in the iframe used the `console` method.
    //
    case 'console':
      this.console.push({
        method: packet.method,
        epoch: +new Date(),
        args: packet.args
      });

      if (packet.attach) {
        this.emit.apply(this, ['attach::'+ packet.method].conat(packet.args));
        this.emit.apply(this, ['attach'].conat(packet.args));
      }
    break;

    //
    // An error happend in the iframe, process it.
    //
    case 'error':
      this.emit('error', new Error(packet.args[0])).retry();
    break;

    //
    // We've received a ping response from the iframe, so we know it's still
    // running as intended.
    //
    case 'ping':
      this.ping();
    break;

    //
    // Handle unknown package types by just returning false after we've emitted
    // it as an `regular` message.
    //
    default:
      this.emit('message', packet.data);
      return false;
  }

  return true;
};

/**
 * Small wrapper around sandbox evaluation.
 *
 * @param {String} cmd The command to executed in the iframe.
 * @param {Function} fn Callback
 * @api public
 */
Container.prototype.eval = function evil(cmd, fn) {
  try {
    this.i.window.eval(cmd);
  } catch (e) {
    return fn(e);
  }

  return fn();
};

/**
 * Error handling.
 *
 * @returns {Boolean}
 * @api private
 */
Container.prototype.onerror = function onerror() {
  var a = slice.call(arguments, 0);
  this.onmessage({ type: "error", scope: "iframe.onerror", args: a });

  return true;
};

/**
 * The container has been fully loaded and the iframe has executed it's code.
 *
 * @returns {Boolean}
 * @api private
 */
Container.prototype.onload = function onload() {
  this.readyState = Container.OPEN;
  this.emit('start');

  return true;
};

Container.prototype.onunload = function onunload() {
  this.readyState = Container.CLOSED;
  this.emit('stop');

  return true;
};

/**
 * Start the container.
 *
 * @returns {Container}
 * @api public
 */
Container.prototype.start = function start() {
  this.readyState = Container.OPENING;

  //
  // Attach various event listeners so we can update the state of the container.
  // We don't need to use `.addEventLister` as we only want and require one
  // single event listener.
  //
  this.i.window.onerror = this.bound(this.onerror);
  this.i.frame.onload = this.bound(this.onload);
  this.i.frame.onunload = this.bound(this.unload);

  //
  // If the container is already in the HTML we're going to assume that we still
  // have to load it with the Image. But if it's not in the mount point (DOM) we
  // assume that the iframe has been removed to release memory and what ever,
  // but when we re-add it to the mount point, it will automatically restart the
  // JavaScript that was originally loaded in the container.
  //
  if (!document.getElementById(this.id)) {
    this.mount.appendChild(this.i.frame);
  } else {
    var doc = this.i.document;

    doc.open();
    doc.write('<!doctype html><s'+'cript>'+ this.image +'</s'+'cript>');
    doc.close();
  }

  //
  // Introduce a custom variable in to the iframe that exposes our Container as
  // API. The Image can use this to communicate with the container and pass
  // messages back and forth.
  //
  this.i.window[this.id] = this.bound(this.onmessage);
  this.started = +new Date();

  return this;
};

/**
 * Stop running the code inside the container.
 *
 * @returns {Container}
 * @api private
 */
Container.prototype.stop = function stop() {
  if (!document.getElementById(this.id)) return this;

  this.readyState = Container.CLOSING;
  this.i.window[this.id] = null;
  this.mount.removeChild(this.i.frame);

  //
  // Known to throw errors in certain situations (IE)
  //
  try { this.i.frame.onload = null; }
  catch (e) {}

  try { this.i.frame.onunload = null; }
  catch (e) {}

  try { this.i.window.onerror = null; }
  catch (e) {}

  return this;
};

/**
 * Load the given code as image on to the container.
 *
 * @param {String} code The code that should run on the container.
 * @returns {Container}
 * @api public
 */
Container.prototype.load = function load(code) {
  this.image = new Image(this.id, code);

  return this;
};

/**
 * Completely destroy the given container and ensure that all references are
 * nuked so we can clean up as much memory as possible.
 *
 * @returns {Container}
 * @api private
 */
Container.prototype.destroy = function destroy() {
  this.stop();

  //
  // Remove all possible references to release as much memory as possible.
  //
  this.mount = this.image = this.id = this.i = this.created = null;
  this.removeAllListeners();

  return this;
};

//
// Expose the module.
//
module.exports = Container;
<|endoftext|>


<commit_before>'use strict';
var _ = require('lodash');
var BBPromise = require('bluebird');
var Model = require('hoist-model');
var logger = require('hoist-logger');
var config = require('config');
var fs = require('fs');
var path = require('path');
var SubscriptionController = require('./subscription_controller');
var EventListener = require('./event_listener');

function Poller() {
  this.stopped = false;
}

Poller.prototype = {
  start: function () {
    // var _timer;
    logger.info('starting poller');
    /* istanbul ignore else */
    if (Model._mongoose.connection.readyState !== 1) {
      logger.info('mongo connected');
      Model._mongoose.connect(config.get('Hoist.mongo.db'));
    }
    return Model.Subscription.findAsync({})
      .bind(this)
      .then(function (subscriptions) {
        return BBPromise.settle(_.map(subscriptions, _.bind(this.hydrateSubscription, this)));
      }).then(function (promises) {
        // wait two minutes until next poll
        if (!this.stopped) {
          return BBPromise.delay(120000).bind(this).then(function () {
            return this.start();
          });
        }
        return promises;
      }).catch(function (err) {
        logger.info('error:', err, err.stack);
        console.log('error:', err, err.stack);
      });
  },
  hydrateSubscription: function (subscription) {
    var self = this;
    return Model.ConnectorSetting.findOneAsync({
        key: subscription.connector,
        environment: subscription.environment,
        application: subscription.application
      })
      .then(function (connector) {
        /* istanbul ignore else */
        return Model.Application.findOneAsync({
            _id: connector.application
          })
          .then(function (app) {

            if (connector.settings.authType === 'Private') {
              return self.pollModule(app, subscription, connector);
            } //else {
              // var query = {
              //   application: app._id,
              //   environment: subscription.environment,
              //   connectorKey: connector.key,
              //   connectorType: connector.connectorType
              // };
            // return Model.BouncerToken.findOneAsync(query)
            //   .then(function (bouncer) {
            //     var query = {
            //       application: app._id,
            //       environment: subscription.environment
            //     };
            //     query['meta.authToken.' + connector.key] = {
            //       $exists: true
            //     };
            //     return Model.Bucket.findAsync(query)
            //       .then(function (buckets) {
            //         var info = {
            //           connector: connector._id,
            //           application: app._id,
            //           bouncer: bouncer ? bouncer._id : null
            //         };
            //         logger.info('inside poller#hydrateSubscription', info);
            //         return BBPromise.settle(_.map(buckets, function (bucket) {
            //           return self.pollModule(app, subscription, connector, bouncer, bucket);
            //         }));
            //       });
            //   });
                // }
          });
      }).catch(function (err) {
        logger.info('error', err, err.stack);
        console.log('err', err);
      });
  },
  pollModule: function (app, subscription, connector, bouncer, bucket) {
    // add listeners
    var _subscription = new SubscriptionController(subscription);
    new EventListener(_subscription, connector, bucket).listen();
    _subscription.eventEmitter.on('done', function () {
      logger.info('poll listener received done');
      _subscription.eventEmitter.removeAllListeners();
    });
    var connectorsPath = path.resolve(config.get('Hoist.connectors.path'));
    var connectorPath = path.join(connectorsPath, connector.connectorType, config.get('Hoist.connectors.currentDirectoryName'), 'lib/poll.js');
    connectorPath = fs.realpathSync(connectorPath);
    logger.info('connectorPath', connectorPath);
    // send bouncer and bucket when not private app
    return require(connectorPath)(app.toObject(), _subscription, connector.toObject());
  },
  stop: /* istanbul ignore next */ function () {
    this.stopped = true;
  },
  restart: function () {
    this.stopped = false;
    return this.start();
  }
};

module.exports = new Poller();<commit_msg>set debug on mongoose to true<commit_after>'use strict';
var _ = require('lodash');
var BBPromise = require('bluebird');
var Model = require('hoist-model');
var logger = require('hoist-logger');
var config = require('config');
var fs = require('fs');
var path = require('path');
var SubscriptionController = require('./subscription_controller');
var EventListener = require('./event_listener');

function Poller() {
  this.stopped = false;
}
Model._mongoose.set('debug',true);

Poller.prototype = {
  start: function () {
    // var _timer;
    logger.info('starting poller');
    /* istanbul ignore else */

    if (Model._mongoose.connection.readyState !== 1) {
      logger.info('mongo connected');
      Model._mongoose.connect(config.get('Hoist.mongo.db'));
    }
    return Model.Subscription.findAsync({})
      .bind(this)
      .then(function (subscriptions) {
        return BBPromise.settle(_.map(subscriptions, _.bind(this.hydrateSubscription, this)));
      }).then(function (promises) {
        // wait two minutes until next poll
        if (!this.stopped) {
          return BBPromise.delay(120000).bind(this).then(function () {
            return this.start();
          });
        }
        return promises;
      }).catch(function (err) {
        logger.info('error:', err, err.stack);
        console.log('error:', err, err.stack);
      });
  },
  hydrateSubscription: function (subscription) {
    var self = this;
    return Model.ConnectorSetting.findOneAsync({
        key: subscription.connector,
        environment: subscription.environment,
        application: subscription.application
      })
      .then(function (connector) {
        /* istanbul ignore else */
        return Model.Application.findOneAsync({
            _id: connector.application
          })
          .then(function (app) {

            if (connector.settings.authType === 'Private') {
              return self.pollModule(app, subscription, connector);
            } //else {
              // var query = {
              //   application: app._id,
              //   environment: subscription.environment,
              //   connectorKey: connector.key,
              //   connectorType: connector.connectorType
              // };
            // return Model.BouncerToken.findOneAsync(query)
            //   .then(function (bouncer) {
            //     var query = {
            //       application: app._id,
            //       environment: subscription.environment
            //     };
            //     query['meta.authToken.' + connector.key] = {
            //       $exists: true
            //     };
            //     return Model.Bucket.findAsync(query)
            //       .then(function (buckets) {
            //         var info = {
            //           connector: connector._id,
            //           application: app._id,
            //           bouncer: bouncer ? bouncer._id : null
            //         };
            //         logger.info('inside poller#hydrateSubscription', info);
            //         return BBPromise.settle(_.map(buckets, function (bucket) {
            //           return self.pollModule(app, subscription, connector, bouncer, bucket);
            //         }));
            //       });
            //   });
                // }
          });
      }).catch(function (err) {
        logger.info('error', err, err.stack);
        console.log('err', err);
      });
  },
  pollModule: function (app, subscription, connector, bouncer, bucket) {
    // add listeners
    var _subscription = new SubscriptionController(subscription);
    new EventListener(_subscription, connector, bucket).listen();
    _subscription.eventEmitter.on('done', function () {
      logger.info('poll listener received done');
      _subscription.eventEmitter.removeAllListeners();
    });
    var connectorsPath = path.resolve(config.get('Hoist.connectors.path'));
    var connectorPath = path.join(connectorsPath, connector.connectorType, config.get('Hoist.connectors.currentDirectoryName'), 'lib/poll.js');
    connectorPath = fs.realpathSync(connectorPath);
    logger.info('connectorPath', connectorPath);
    // send bouncer and bucket when not private app
    return require(connectorPath)(app.toObject(), _subscription, connector.toObject());
  },
  stop: /* istanbul ignore next */ function () {
    this.stopped = true;
  },
  restart: function () {
    this.stopped = false;
    return this.start();
  }
};

module.exports = new Poller();
<|endoftext|>


<commit_before>'use strict'

// TODO module descriptors
const suffixTest = /\.json$/;
const resolve = require('path').resolve;
const fs = require('fs');
const root = resolve(process.argv[2] || '/home/adioo/Repos') + '/';
const path = root + 'composition/';
const instances = {};
const files = fs.readdirSync(path);
const domain = '';//'https://static.jillix.com/'
const type = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
const dependencies = {};
const modules = [
    //"flow-app",
    "flow-browser",
    "flow-http",
    "flow-compress",
    //"flow-pack",
    "flow-router",
    //"flow-service-api",
    "flow-tools",
    "flow-static",
    "flow-streams",
    //"flow-schema",
    //"flow-url",
    //"flow-sendgrid",
    //"flow-auth",
    "flow-view",
    "flow-api",
    "flow-visualizer",
    //"flow-remodal",
    "builder"
    //"schema"
];

function getModuleId (name) {

    let owner = 'jillix';
    switch (name) {
        case 'flow-view':
        case 'flow-auth':
        case 'flow-streams':
        case 'flow-visualizer':
        case 'flow-compress':
            owner = 'adioo';
            break;
    }

    return owner + '/' + name;
}

modules.forEach(dep => {

    let module = getModuleId(dep);

    write(
        '<' + module + '>',
        'http://www.w3.org/1999/02/22-rdf-syntax-ns#type',
        '<http://schema.jillix.net/vocab/Module>'
    );

    write(
        '<' + module  + '>',
        'http://schema.jillix.net/vocab/gitRepository',
        '"git+https://github.com/' + module + '.git"'
    );

    write(
        '<' + module + '>',
        'http://schema.org/name',
        '"' + dep + '"'
    );
});

files.forEach(file => {

    if (!suffixTest.test(file)) {
        return;
    }

    try {
        let instance = JSON.parse(fs.readFileSync(path + file));
        instances[instance.name] = instance;
    } catch (error) {
        throw new Error(path + file + '\n' + error);
    }
});

for (let instance in instances) {
    Convert(instances[instance]);
}

for (let from in dependencies) {
    for (let to in dependencies[from]) {
        write(from, 'http://schema.jillix.net/vocab/dependency', to);
    }
}

function write (subject, predicate, object) {
    process.stdout.write(subject + ' <' + predicate + '> ' + object + ' .\n');
}

function getModuleIri (instance, method) {
    let module = getModuleId(instances[instance].module);

    if (method) {
        return '<' + module + '#' + method + '>';
    } else {
        return '<' + module + '>';
    }
}

function extendInstance (instance, path) {
    path = path.split('/');
    let parsed = {
        type: path[0][0]
    };
    path[0] = path[0].substr(1);

    if (path.length > 1) {
        parsed.instance = path[0];
        parsed.path = path[1];
    } else {
        parsed.instance = instance;
        parsed.path = path[0]
    }

    if (parsed.type !== '>') {
        parsed.path = getModuleIri(parsed.instance, parsed.path);
    } else {
        parsed.path = '<' + domain + parsed.instance + '/' + parsed.path + '>';
    }

    return parsed;
}

function UID (len) {
    len = len || 23;
    for (var i = 0, random = ''; i < len; ++i) {
        random += '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'[0 | Math.random() * 62];
    }
    return random;
}

function Convert (instance) {
    let instance_name = '<' + domain + instance.name + '>';

    if (instance.name) {
        write(
            instance_name,
            'http://schema.org/name',
            '"' + instance.name + '"'
        );
    }

    write(
        instance_name,
        type,
        '<http://schema.jillix.net/vocab/ModuleInstanceConfig>'
    );

    // module
    if (instance.module) {
        write(
            instance_name,
            'http://schema.jillix.net/vocab/module',
            getModuleIri(instance.name)
        );
    }

    // roles
    if (instance.roles) {
        for (let role in instance.roles) {
            write(
                instance_name,
                'http://schema.jillix.net/vocab/roles',
                '"' + role + '"'
            );
        }
    }

    //flow
    if (instance.flow) {
        for (let event in instance.flow) {
            let event_iri = '<' + domain + instance.name + '/' + event + '>';
            write(
                event_iri,
                'http://schema.org/name',
                '"' + event + '"'
            );

            write(
                instance_name,
                'http://schema.jillix.net/vocab/event',
                event_iri
            );

            write(
                event_iri,
                type,
                '<http://schema.jillix.net/vocab/FlowEvent>'
            );

            // end event
            if (instance.flow[event].e) {
                write(
                    event_iri,
                    'http://schema.jillix.net/vocab/onEnd',
                    '<' + domain + instance.flow[event].e + '>'
                );
            }

            // error event
            if (instance.flow[event].r) {
                write(
                    event_iri,
                    'http://schema.jillix.net/vocab/onError',
                    '<' + domain + instance.flow[event].r + '>'
                );
            }

            // sequence
            if (instance.flow[event].d) {
                let previous;
                instance.flow[event].d.forEach((handler, index) => {

                    let handler_id = '_:' + UID(8);
                    let path;
                    let args;

                    write(
                        index === 0 ? event_iri : previous,
                        'http://schema.jillix.net/vocab/sequence',
                        handler_id
                    );
                    previous = handler_id;

                    if (typeof handler === 'string') {
                        path = handler;
                        args = null;
                    } else {
                        path = handler[0];
                        args = handler[1];
                    }

                    path = extendInstance(instance.name, path);
                    if (instance.name !== path.instance) {
                        dependencies[instance_name] = dependencies[instance_name] || {};
                        dependencies[instance_name]['<' + domain + path.instance + '>'] = 1;
                    }

                    write(
                        handler_id,
                        'http://schema.jillix.net/vocab/event',
                        event_iri
                    );

                    write(
                        handler_id,
                        type,
                        '<http://schema.jillix.net/vocab/Sequence>'
                    );

                    if (args) {
                        write(
                            handler_id,
                            'http://schema.jillix.net/vocab/args',
                            '"' + JSON.stringify(args).replace(/"/g, '\\"') + '"'
                        );
                    }

                    write(
                        handler_id,
                        'http://schema.jillix.net/vocab/instance',
                        '<' + domain + path.instance + '>'
                    );

                    switch (path.type) {
                        case '.':
                            write(
                                handler_id,
                                'http://schema.jillix.net/vocab/onceHandler',
                                path.path
                            );
                            break;

                        case ':':
                            write(
                                handler_id,
                                'http://schema.jillix.net/vocab/dataHandler',
                                path.path
                            );
                            break;
                        case '*':
                            write(
                                handler_id,
                                'http://schema.jillix.net/vocab/streamHandler',
                                path.path
                            );
                            break;

                        case '>':
                            write(
                                handler_id,
                                'http://schema.jillix.net/vocab/emit',
                                path.path
                            );
                            break;
                    }
                });
            }
        }
    }
}
<commit_msg>convert to new flow config<commit_after>'use strict'

// TODO module descriptors
const resolve = require('path').resolve;
const fs = require('fs');
const suffixTest = /\.json$/;
const root = resolve(process.argv[2] || '/home/adioo/Repos') + '/';
const path = root + 'composition/';
const states = {};
const files = fs.readdirSync(path);
const type = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
const dependencies = {};

files.forEach(file => {

    if (!suffixTest.test(file)) {
        return;
    }

    try {
        let state = JSON.parse(fs.readFileSync(path + file));
        states[state.name] = state;
    } catch (error) {
        throw new Error(path + file + '\n' + error);
    }
});

for (let state in states) {
    Convert(states[state]);
}

/*for (let from in dependencies) {
    for (let to in dependencies[from]) {
        write(from, 'http://schema.jillix.net/vocab/dependency', to);
    }
}*/

function getMethodIri (module, method) {

    // git+https://github.com/ + module + .git
    let owner = 'jillix';
    switch (module) {
        case 'flow-view':
        case 'flow-auth':
        case 'flow-streams':
        case 'flow-visualizer':
        case 'flow-compress':
            owner = 'adioo';
            break;
    }

    return owner + '/' + module + '?' + method;
}

function write (subject, predicate, object) {
    process.stdout.write(subject + ' <' + predicate + '> ' + object + ' .\n');
}

function extendInstance (state, path) {
    path = path.split('/');
    let parsed = {
        type: path[0][0]
    };
    path[0] = path[0].substr(1);

    if (path.length > 1) {
        parsed.state = path[0];
        parsed.path = path[1];
    } else {
        parsed.state = state;
        parsed.path = path[0]
    }

    if (parsed.type !== '>') {
        parsed.path = getMethodIri(states[parsed.state].module, parsed.path);
    } else {
        parsed.path = '_:' + parsed.state + '/' + parsed.path;
    }

    return parsed;
}

function UID (len) {
    len = len || 23;
    for (var i = 0, random = ''; i < len; ++i) {
        random += '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'[0 | Math.random() * 62];
    }
    return random;
}

function Convert (state) {
    if (!state.flow) {
        return;
    }

    for (let sequence in state.flow) {
        let sequence_id = '_:' + UID(8);
        write(
            sequence_id,
            'http://schema.org/name',
            '"' + sequence + '"'
        );

        // TODO add sequence to cluster
        /*write(
            sequence_id,
            'http://schema.jillix.net/vocab/cluster',
            cluster_id
        );
        //if (state.name !== path.instance) {
            //dependencies[state.name] = dependencies[state.name] || {};
            //dependencies[state.name]['<' + path.instance + '>'] = 1;
        //}
        */

        // roles
        if (state.roles) {
            for (let role in state.roles) {
                write(
                    sequence_id,
                    'http://schema.jillix.net/vocab/roles',
                    '"' + role + '"'
                );
            }
        }

        // type
        write(
            sequence_id,
            type,
            '<http://schema.jillix.net/vocab/Sequence>'
        );

        // end event
        if (state.flow[sequence].e) {
            write(
                sequence_id,
                'http://schema.jillix.net/vocab/onEnd',
                '_:' + state.flow[sequence].e
            );
        }

        // error event
        if (state.flow[sequence].r) {
            write(
                sequence_id,
                'http://schema.jillix.net/vocab/onError',
                '_:' + state.flow[sequence].r
            );
        }

        // sequence
        if (state.flow[sequence].d) {
            let previous;
            state.flow[sequence].d.forEach((handler, index) => {

                let handler_id = '_:' + UID(8);
                let path;
                let args;

                write(
                    index === 0 ? sequence_id : previous,
                    'http://schema.jillix.net/vocab/next',
                    handler_id
                );
                previous = handler_id;

                if (typeof handler === 'string') {
                    path = handler;
                    args = null;
                } else {
                    path = handler[0];
                    args = handler[1];
                }

                path = extendInstance(state.name, path);

                write(
                    handler_id,
                    'http://schema.jillix.net/vocab/sequence',
                    sequence_id
                );

                write(
                    handler_id,
                    type,
                    '<http://schema.jillix.net/vocab/Handler>'
                );

                // TODO does this generate redudant args objects?
                if (args) {
                    write(
                        handler_id,
                        'http://schema.jillix.net/vocab/args',
                        '"' + JSON.stringify(args).replace(/"/g, '\\"') + '"'
                    );
                }

                write(
                    handler_id,
                    'http://schema.jillix.net/vocab/state',
                    '"' + state.name + '"'
                );

                switch (path.type) {
                    case '.':
                    case ':':
                        write(
                            handler_id,
                            'http://schema.jillix.net/vocab/data',
                            path.path
                        );
                        break;
                    case '*':
                        write(
                            handler_id,
                            'http://schema.jillix.net/vocab/stream',
                            path.path
                        );
                        break;

                    case '>':
                        write(
                            handler_id,
                            'http://schema.jillix.net/vocab/emit',
                            path.path
                        );
                        break;
                }
            });
        }
    }
}
<|endoftext|>


<commit_before>var crypto = require('crypto');
var fs = require('fs');

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

var mkdirp = require('mkdirp');

var request = require('request');

module.exports.ttl = 60 * 60 * 24;
module.exports.cacheDirectory = './temp/cache/';

module.exports._requestFromUrl = function (url, callback) {

    var hash = crypto.createHash('sha1').update(url).digest('hex');

    fs.stat(module.exports.cacheDirectory + hash, function (err, stats) {

        if (stats && ((Date.now() - stats.ctime.getTime()) / 1000) < module.exports.ttl) {

            fs.readFile(module.exports.cacheDirectory + hash, 'utf8', function (err, res) {

                callback(err, JSON.parse(res));

            });

        } else {

            request(url, function (err, res, body) {

                mkdirp(module.exports.cacheDirectory, function () {

                    fs.writeFile(module.exports.cacheDirectory + hash, JSON.stringify(res), function () {

                        callback(err, res);

                    });

                });

            });

        }

    });

};

module.exports.get = function (input) {

    var deferred = new q.defer();

    function callback (err, contents) {

        if (err) {

            deferred.reject(new Error(err));

        } else {

            deferred.resolve(contents);

        }

    }

    if (Array.isArray(input)) {

        async.map(input, module.exports._requestFromUrl, callback);

    } else {

        module.exports._requestFromUrl(input, callback);

    }

    return deferred.promise;

};
<commit_msg>Added error check around writing cache contents.<commit_after>var crypto = require('crypto');
var fs = require('fs');

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

var mkdirp = require('mkdirp');

var request = require('request');

module.exports.ttl = 60 * 60 * 24;
module.exports.cacheDirectory = './temp/cache/';

module.exports._requestFromUrl = function (url, callback) {

    var hash = crypto.createHash('sha1').update(url).digest('hex');

    fs.stat(module.exports.cacheDirectory + hash, function (err, stats) {

        if (stats && ((Date.now() - stats.ctime.getTime()) / 1000) < module.exports.ttl) {

            fs.readFile(module.exports.cacheDirectory + hash, 'utf8', function (err, res) {

                callback(err, JSON.parse(res));

            });

        } else {

            request(url, function (err, res, body) {

                if (!err) {

                    mkdirp(module.exports.cacheDirectory, function () {

                        fs.writeFile(module.exports.cacheDirectory + hash, JSON.stringify(res.toJSON()), function () {

                            callback(err, res);

                        });

                    });

                } else {

                    callback(err);

                }

            });

        }

    });

};

module.exports.get = function (input) {

    var deferred = new q.defer();

    function callback (err, contents) {

        if (err) {

            deferred.reject(new Error(err));

        } else {

            deferred.resolve(contents);

        }

    }

    if (Array.isArray(input)) {

        async.map(input, module.exports._requestFromUrl, callback);

    } else {

        module.exports._requestFromUrl(input, callback);

    }

    return deferred.promise;

};
<|endoftext|>


<commit_before>
/*
  jGet JavaScript library v 0.1

  Copyright Mayank Srivastav

  Date: 27-June-2017
*/

(function (windo) {
  "use strict";
  // Global shorthands
  let _arr = [],
    _keys = Object.keys,
    _values = Object.values,
    _splice = _arr.splice,
    _proto = Object.prototype,
    _filter = _arr.filter,
    _map = _arr.map,
    _document = window.document;

  // jGet local copy
  // TODO: pass context argument
  let jGet = function(selector) {
    return new jGet.py.init(selector);
  };

  jGet.py = jGet.prototype = {
    constructor: jGet,

    map: function(elem, callback) {
      return jGet.map(elem, callback);
    }
  };
})(window);
<commit_msg>Added code for partial extend method<commit_after>
/*
  jGet JavaScript library v 0.1

  Copyright Mayank Srivastav

  Date: 27-June-2017
*/

(function (windo) {
  "use strict";
  // Global shorthands
  let _arr = [],
    _keys = Object.keys,
    _values = Object.values,
    _splice = _arr.splice,
    _proto = Object.prototype,
    _filter = _arr.filter,
    _map = _arr.map,
    _document = window.document;

  // jGet local copy
  // TODO: pass context argument
  let jGet = function(selector) {
    return new jGet.py.init(selector);
  };

  jGet.py = jGet.prototype = {
    constructor: jGet,

    map: function(elem, callback) {
      return jGet.map(elem, callback);
    }
  };

  // extends the source object with
  // destination object

  // TODO: Handle single arg
  jGet.extend = function() {
    let source, dest, args, len;
    let i;

    // do nothing if source is not
    // specified
    if (arguments.length === 0) {
      return;
    }

    i = len = arguments.length;

    source = arguments[0];
    // dest = arguments[1];
})(window);
<|endoftext|>


<commit_before>var NodeGit = require("../");
var normalizeOptions = NodeGit.Utils.normalizeOptions;
var lookupWrapper = NodeGit.Utils.lookupWrapper;
var shallowClone = require("./utils/shallow_clone");

var Remote = NodeGit.Remote;
var connect = Remote.prototype.connect;
var download = Remote.prototype.download;
var fetch = Remote.prototype.fetch;
var push = Remote.prototype.push;

/**
 * Retrieves the remote by name
 * @async
 * @param {Repository} repo The repo that the remote lives in
 * @param {String|Remote} name The remote to lookup
 * @param {Function} callback
 * @return {Remote}
 */
Remote.lookup = lookupWrapper(Remote);

/**
 * Connects to a remote
 *
 * @async
 * @param {Enums.DIRECTION} direction The direction for the connection
 * @param {RemoteCallbacks} callbacks The callback functions for the connection
 * @param {Function} callback
 * @return {Number} error code
 */
Remote.prototype.connect = function(direction, callbacks) {
  callbacks = normalizeOptions(callbacks, NodeGit.RemoteCallbacks);

  return connect.call(this, direction, callbacks);
};

/**
 * Connects to a remote
 *
 * @async
 * @param {Array} refSpecs The ref specs that should be pushed
 * @param {FetchOptions} opts The fetch options for download, contains callbacks
 * @param {Function} callback
 * @return {Number} error code
 */
Remote.prototype.download = function(refspecs, opts) {
  var callbacks;

  if (opts) {
    opts = shallowClone(opts);
    callbacks = opts.callbacks;
    delete opts.callbacks;
  } else {
    opts = {};
  }

  opts = normalizeOptions(opts, NodeGit.FetchOptions);

  if (callbacks) {
    opts.callbacks =
      normalizeOptions(callbacks, NodeGit.RemoteCallbacks);
  }

  return download.call(this, refspecs, opts);
};

/**
 * Connects to a remote
 *
 * @async
 * @param {Array} refSpecs The ref specs that should be pushed
 * @param {FetchOptions} opts The fetch options for download, contains callbacks
 * @param {String} message The message to use for the update reflog messages
 * @param {Function} callback
 * @return {Number} error code
 */
Remote.prototype.fetch = function(refspecs, opts, reflog_message) {
  var callbacks;

  if (opts) {
    opts = shallowClone(opts);
    callbacks = opts.callbacks;
    delete opts.callbacks;
  } else {
    opts = {};
  }

  opts = normalizeOptions(opts, NodeGit.FetchOptions);

  if (callbacks) {
    opts.callbacks =
      normalizeOptions(callbacks, NodeGit.RemoteCallbacks);
  }

  return fetch.call(this, refspecs, opts, reflog_message);
};

/**
 * Pushes to a remote
 *
 * @async
 * @param {Array} refSpecs The ref specs that should be pushed
 * @param {PushOptions} options Options for the checkout
 * @param {Function} callback
 * @return {Number} error code
 */
Remote.prototype.push = function(refSpecs, opts) {
  var callbacks;
  if (opts) {
    opts = shallowClone(opts);
    callbacks = opts.callbacks;
    delete opts.callbacks;
  } else {
    opts = {};
  }

  opts = normalizeOptions(opts, NodeGit.PushOptions);

  if (callbacks) {
    opts.callbacks =
      normalizeOptions(callbacks, NodeGit.RemoteCallbacks);
  }

  return push.call(this, refSpecs, opts);
};

module.exports = Remote;
<commit_msg>Fix remote tests<commit_after>var NodeGit = require("../");
var normalizeOptions = NodeGit.Utils.normalizeOptions;
var lookupWrapper = NodeGit.Utils.lookupWrapper;
var shallowClone = require("./utils/shallow_clone");

var Remote = NodeGit.Remote;
var connect = Remote.prototype.connect;
var download = Remote.prototype.download;
var fetch = Remote.prototype.fetch;
var push = Remote.prototype.push;

/**
 * Retrieves the remote by name
 * @async
 * @param {Repository} repo The repo that the remote lives in
 * @param {String|Remote} name The remote to lookup
 * @param {Function} callback
 * @return {Remote}
 */
Remote.lookup = lookupWrapper(Remote);

/**
 * Connects to a remote
 *
 * @async
 * @param {Enums.DIRECTION} direction The direction for the connection
 * @param {RemoteCallbacks} callbacks The callback functions for the connection
 * @param {ProxyOptions} proxyOpts Proxy settings
 * @param {Array[String]} customHeaders extra HTTP headers to use
 * @param {Function} callback
 * @return {Number} error code
 */
Remote.prototype.connect = function(
  direction,
  callbacks,
  customHeaders
) {
  callbacks = normalizeOptions(callbacks, NodeGit.RemoteCallbacks);
  customHeaders = customHeaders || [];

  return connect.call(this, direction, callbacks, customHeaders);
};

/**
 * Connects to a remote
 *
 * @async
 * @param {Array} refSpecs The ref specs that should be pushed
 * @param {FetchOptions} opts The fetch options for download, contains callbacks
 * @param {Function} callback
 * @return {Number} error code
 */
Remote.prototype.download = function(refspecs, opts) {
  var callbacks;

  if (opts) {
    opts = shallowClone(opts);
    callbacks = opts.callbacks;
    delete opts.callbacks;
  } else {
    opts = {};
  }

  opts = normalizeOptions(opts, NodeGit.FetchOptions);

  if (callbacks) {
    opts.callbacks =
      normalizeOptions(callbacks, NodeGit.RemoteCallbacks);
  }

  return download.call(this, refspecs, opts);
};

/**
 * Connects to a remote
 *
 * @async
 * @param {Array} refSpecs The ref specs that should be pushed
 * @param {FetchOptions} opts The fetch options for download, contains callbacks
 * @param {String} message The message to use for the update reflog messages
 * @param {Function} callback
 * @return {Number} error code
 */
Remote.prototype.fetch = function(refspecs, opts, reflog_message) {
  var callbacks;

  if (opts) {
    opts = shallowClone(opts);
    callbacks = opts.callbacks;
    delete opts.callbacks;
  } else {
    opts = {};
  }

  opts = normalizeOptions(opts, NodeGit.FetchOptions);

  if (callbacks) {
    opts.callbacks =
      normalizeOptions(callbacks, NodeGit.RemoteCallbacks);
  }

  return fetch.call(this, refspecs, opts, reflog_message);
};

/**
 * Pushes to a remote
 *
 * @async
 * @param {Array} refSpecs The ref specs that should be pushed
 * @param {PushOptions} options Options for the checkout
 * @param {Function} callback
 * @return {Number} error code
 */
Remote.prototype.push = function(refSpecs, opts) {
  var callbacks;
  if (opts) {
    opts = shallowClone(opts);
    callbacks = opts.callbacks;
    delete opts.callbacks;
  } else {
    opts = {};
  }

  opts = normalizeOptions(opts, NodeGit.PushOptions);

  if (callbacks) {
    opts.callbacks =
      normalizeOptions(callbacks, NodeGit.RemoteCallbacks);
  }

  return push.call(this, refSpecs, opts);
};

module.exports = Remote;
<|endoftext|>