<commit_before>/**
 * Created by barakedry on 6/19/15.
 */
'use strict';
var _ = require('lodash');
var util = require('util');
var utils = require('./utils');
var DiffTracker = require('./diff-tracker');
var EventEmitter = require('events').EventEmitter;

function MergeDiff(object, options) {
    this.options = _.defaults(options || {}, {
        emitEvents: true, // settings this to false should allow faster merging but it is not implemented yet
        undefinedKeyword: '__$$U',
        deleteKeyword: '__$$D',
        protoKeyword: '__$$P',
        mergeDeletions: true,
        mergeAdditions: true,
        emitAdditions: true,
        emitUpdates: true,
        emitDifferences: true,
        maxKeysInLevel: 1000,
        maxLevels: 50,
        maxListeners: 1000000
    });

    this.object = object || {};
    //this.prototypes = []; // prototypes are stored in a special collection

    //Event emitters
    this.additions = new EventEmitter();
    this.additions.setMaxListeners(this.options.maxListeners);
    this.updates = new EventEmitter();
    this.updates.setMaxListeners(this.options.maxListeners);
    this.differences = new EventEmitter();
    this.differences.setMaxListeners(this.options.maxListeners);
    this.deletions = new EventEmitter();
    this.deletions.setMaxListeners(this.options.maxListeners);
    this.restructure = new EventEmitter();
    this.restructure.setMaxListeners(this.options.maxListeners);
}

util.inherits(MergeDiff, EventEmitter);

MergeDiff.prototype.merge = function (delta, path, options) {

    options = _.defaults(options || {}, this.options);

    if (!_.isObject(delta) && !path) {
        console.error('invalid merge, source and delta must be objects');
        return;
    }

    this._mergeObject(this.object, utils.wrapByPath(delta, path), "", options, 0);
};

MergeDiff.prototype.override = function (delta, path, options) {

    options = _.defaults(options || {}, this.options);

    if (!_.isObject(delta) && !path) {
        console.error('invalid merge, source and delta must be objects');
        return;
    }

    this._mergeObject(this.object, utils.wrapByPath(delta, path), "", options, 0, path);
};

MergeDiff.prototype.delete = function (path) {

    if (!(path && _.isString(path))) {
        console.error('invalid path, cannot delete');
        return;
    }

    this._mergeObject(this.object, utils.wrapByPath(this.options.deleteKeyword, path), "", null, this.options, 0);
};

MergeDiff.prototype.get = function (path) {

    if (!(path && _.isString(path))) {
        console.error('invalid path, cannot delete');
        return;
    }

    return _.get(this.object, path);
};

MergeDiff.prototype._mergeObject = function (source, delta, path, options, level, override) {
    var keys,
        key,
        i,
        length,
        levelDiffs,
        isSourceArray;

    if (!(_.isObject(source) && _.isObject(delta))) {
        console.error('invalid merge, source and delta must be objects');
        this.emit('error', new Error('invalid merge, source and delta must be objects'));
        return;
    }

    if (level > options.maxLevels) {
        console.error('Trying to merge too deep, stopping at level %d', level);
        this.emit('error', new Error('Trying to merge too deep, stopping at level ' + level));
        return;
    }

    keys = _.keys(delta);
    length = keys.length;
    isSourceArray = _.isArray(source);

    if (options.emitEvents) {
        levelDiffs = DiffTracker.create();
    }

    if (isSourceArray) {
        levelDiffs = levelDiffs || {};
        levelDiffs.arrayOffset = 0;
    }

    if (length > options.maxKeysInLevel) {
        console.error('Stopping Merge, Too many keys in object - %d out of %d allowed keys.', length, options.maxKeysInLevel);
        this.emit('error', new Error('Stopping Merge, Too many keys in object - ' + length + ' out of ' + options.maxKeysInLevel + ' allowed keys.'));
        return levelDiffs;
    }

    // override is either undefined, a path or true
    if (!_.isUndefined(override) && (override === true || override === path)) {
        override = true;
    }

    for (i = 0; i < length; i++) {
        key = keys[i];

        if (utils.isValid(delta[key]) && delta[key] !== source[key]) {
            levelDiffs = this._mergeAtKey(source, delta, path, key, levelDiffs, options, isSourceArray, override);
        }

    }

    if (override) {
        // find keys at this level that exists at the source object and delete them
        levelDiffs = this._detectDeletionsAtLevel(source, key, delta, levelDiffs, path, options, isSourceArray);
    }

    if (options.emitEvents) {
        this.emitForPath(path || "*", levelDiffs);
    }

    return levelDiffs;
};

MergeDiff.prototype._mergeAtKey = function (source, delta, path, key, levelDiffs, options, isSourceArray, override) {

    var childDiffs,
        mergingValue,
        existingValue,
        srcKey,
        mergedValue, // this value is what goes out to the tracker its not allways the same as mergingValue
        isExistingValueArray,
        isObjectMerge = false;

    mergingValue = delta[key];

    if (isSourceArray) {
        srcKey = Number(key) + levelDiffs.arrayOffset;
    } else {
        srcKey = key;
    }

    if (_.isFunction(mergingValue)) {
        mergedValue = utils.SERIALIZABLE_FUNCTION;
    } else {
        isObjectMerge = _.isObject(mergingValue);
        if (_.isUndefined(mergingValue)) {
            mergedValue = options.undefinedKeyword;
        } else if (mergingValue === options.undefinedKeyword) {
            mergedValue = mergingValue;
            mergingValue = undefined;
        } else {
            mergedValue = mergingValue;
        }
    }

    // new
    if (!source.hasOwnProperty(srcKey)) {
        if (options.mergeAdditions && delta[key] !== options.deleteKeyword) {

            // add new object
            if (isObjectMerge) {

                source[srcKey] = Object.create(this.getPrototypeOf(mergingValue));
                childDiffs = this._mergeObject(source[srcKey],
                                               mergingValue,
                                               utils.concatPath(path, key),
                                               options,
                                               _.isArray(source[srcKey]),
                                               override);

                levelDiffs.addChildTracking(childDiffs, key);

            // add new primitive
            } else {

                source[srcKey] = mergingValue;

                levelDiffs.hasAdditions = true;
                levelDiffs.additions[key] = mergedValue;
                levelDiffs.hasDifferences = true;
                levelDiffs.differences[key] = mergedValue;
            }
        }
    // existing
    } else {

        existingValue = source[srcKey];
        isExistingValueArray = _.isArray(existingValue);

        // delete
        if (delta[key] === options.deleteKeyword) {

            levelDiffs = this._deleteAtKey(source, path, key, srcKey, options, existingValue, isSourceArray, levelDiffs);

        // update
        } else {

            // update object
            if (isObjectMerge) {

                // we should replace the source value
                if (!isExistingValueArray && !utils.hasSamePrototype(existingValue, mergingValue)) {

                    // handle prototypes
                    source[srcKey] = Object.create(this.getPrototypeOf(mergingValue));

                }

                childDiffs = this._mergeObject(source[srcKey],
                                               mergingValue,
                                               utils.concatPath(path, key),
                                               options,
                                               isExistingValueArray,
                                               override);

                levelDiffs.addChildTracking(childDiffs, key);

            // update primitive
            } else {

                source[srcKey] = mergingValue;

                levelDiffs.hasUpdates = true;
                levelDiffs.updates[key] = {'old': existingValue, 'new': mergedValue};
                levelDiffs.hasDifferences = true;
                levelDiffs.differences[key] = mergedValue;
            }

        }

    }

    return levelDiffs;
};

MergeDiff.prototype._deleteAtKey = function (source, path, key, srcKey, options, existingValue, isSourceArray, levelDiffs) {
    if (options.mergeDeletions) {
        if (isSourceArray) {
            source.splice(srcKey, 1);
            levelDiffs.arrayOffset--;
        } else {
            delete source[srcKey];
        }

    }

    levelDiffs.deletions[key] = existingValue;
    levelDiffs.differences[key] = options.deleteKeyword;
    levelDiffs.hasDeletions = true;

    this.emitInnerDeletions(path, existingValue);

    return levelDiffs;
};

MergeDiff.prototype._detectDeletionsAtLevel = function (source, key, delta, levelDiffs, path, options, isSourceArray) {
    var keys = _.keys(source),
        length = keys.length,
        existingValue,
        srcKey,
        i;

    // run source object at this level
    for (i = 0; i < length; i++) {
        key = keys[i];
        if (isSourceArray) {
            srcKey = Number(key) + levelDiffs.arrayOffset;
        } else {
            srcKey = key;
        }

        if (!delta.hasOwnProperty(key)) {
            existingValue = source[key];

            levelDiffs = this._deleteAtKey(source, path, key, srcKey, options, existingValue,
                isSourceArray, levelDiffs);

        }

    }
    return levelDiffs;
};

MergeDiff.prototype.getPrototypeOf = function (object) {
    return Object.getPrototypeOf(object);
};

MergeDiff.prototype.emitForPath = function (path, tracker) {

    if (tracker.hasAdditions && this.options.emitAdditions) {
        this.additions.emit(path, tracker.additions);
    }

    if (tracker.hasDeletions && this.options.emitAdditions) {
        this.deletions.emit(path, tracker.deletions);
    }

    if (tracker.hasUpdates && this.options.emitAdditions) {
        this.updates.emit(path, tracker.updates);
    }

    if (tracker.hasDifferences && this.options.emitAdditions) {
        console.log("emitting to ", path);
        this.differences.emit(path, tracker.differences);
    }

};

MergeDiff.prototype.emitInnerDeletions = function (deletedObject, path) {
    var keys, key, i;

    if (!_.isObject(deletedObject)) {
        return;
    }

    keys = _.keys(deletedObject);
    for (i = 0; i < keys.length; i++) {
        key = keys[i];
        if (_.isObject(deletedObject[key])) {
            this.emitInnerDeletions(deletedObject[key], utils.concatPath(path, key));
        }
    }

    this.deletions.emit(path, deletedObject);
    this.difference.emit(path, this.options.deleteKeyword);
};

module.exports = MergeDiff;<commit_msg>some helping comments<commit_after>/**
 * Created by barakedry on 6/19/15.
 */
'use strict';
var _ = require('lodash');
var util = require('util');
var utils = require('./utils');
var DiffTracker = require('./diff-tracker');
var EventEmitter = require('events').EventEmitter;

function MergeDiff(object, options) {
    this.options = _.defaults(options || {}, {
        emitEvents: true, // settings this to false should allow faster merging but it is not implemented yet
        undefinedKeyword: '__$$U',
        deleteKeyword: '__$$D',
        protoKeyword: '__$$P',
        mergeDeletions: true,
        mergeAdditions: true,
        emitAdditions: true,
        emitUpdates: true,
        emitDifferences: true,
        maxKeysInLevel: 1000,
        maxLevels: 50,
        maxListeners: 1000000
    });

    this.object = object || {};
    //this.prototypes = []; // prototypes are stored in a special collection

    //Event emitters
    this.additions = new EventEmitter();
    this.additions.setMaxListeners(this.options.maxListeners);
    this.updates = new EventEmitter();
    this.updates.setMaxListeners(this.options.maxListeners);
    this.differences = new EventEmitter();
    this.differences.setMaxListeners(this.options.maxListeners);
    this.deletions = new EventEmitter();
    this.deletions.setMaxListeners(this.options.maxListeners);
    this.restructure = new EventEmitter();
    this.restructure.setMaxListeners(this.options.maxListeners);
}

util.inherits(MergeDiff, EventEmitter);

MergeDiff.prototype.merge = function (delta, path, options) {

    options = _.defaults(options || {}, this.options);

    if (!_.isObject(delta) && !path) {
        console.error('invalid merge, source and delta must be objects');
        return;
    }

    this._mergeObject(this.object, utils.wrapByPath(delta, path), "", options, 0);
};

MergeDiff.prototype.override = function (delta, path, options) {

    options = _.defaults(options || {}, this.options);

    if (!_.isObject(delta) && !path) {
        console.error('invalid merge, source and delta must be objects');
        return;
    }

    this._mergeObject(this.object, utils.wrapByPath(delta, path), "", options, 0, path);
};

MergeDiff.prototype.delete = function (path) {

    if (!(path && _.isString(path))) {
        console.error('invalid path, cannot delete');
        return;
    }

    this._mergeObject(this.object, utils.wrapByPath(this.options.deleteKeyword, path), "", null, this.options, 0);
};

MergeDiff.prototype.get = function (path) {

    if (!(path && _.isString(path))) {
        console.error('invalid path, cannot delete');
        return;
    }

    return _.get(this.object, path);
};

/************************************************************************************
 * The basic merging recursion implementation:
 * ._mergeObject() -> ._mergeAtKey() -> ._mergeObject() -> ._mergeAtKey() -> ...
 *
 * ._mergeObject() iterate keys at level and calls ._mergeAtKey() for each key,
 * after iteration ends it emits and returns level changes to the caller.
 *
 * ._mergeAtKey() assigns/delete primitives and calls _.mergeObject() for objects
 ************************************************************************************/
MergeDiff.prototype._mergeObject = function (source, delta, path, options, level, override) {
    var keys,
        key,
        i,
        length,
        levelDiffs,
        isSourceArray;

    if (!(_.isObject(source) && _.isObject(delta))) {
        console.error('invalid merge, source and delta must be objects');
        this.emit('error', new Error('invalid merge, source and delta must be objects'));
        return;
    }

    if (level > options.maxLevels) {
        console.error('Trying to merge too deep, stopping at level %d', level);
        this.emit('error', new Error('Trying to merge too deep, stopping at level ' + level));
        return;
    }

    keys = _.keys(delta);
    length = keys.length;
    isSourceArray = _.isArray(source);

    if (options.emitEvents) {
        levelDiffs = DiffTracker.create();
    }

    if (isSourceArray) {
        levelDiffs = levelDiffs || {};
        levelDiffs.arrayOffset = 0;
    }

    if (length > options.maxKeysInLevel) {
        console.error('Stopping Merge, Too many keys in object - %d out of %d allowed keys.', length, options.maxKeysInLevel);
        this.emit('error', new Error('Stopping Merge, Too many keys in object - ' + length + ' out of ' + options.maxKeysInLevel + ' allowed keys.'));
        return levelDiffs;
    }

    // override is either undefined, a path or true
    if (!_.isUndefined(override) && (override === true || override === path)) {
        override = true;
    }

    for (i = 0; i < length; i++) {
        key = keys[i];

        if (utils.isValid(delta[key]) && delta[key] !== source[key]) {
            levelDiffs = this._mergeAtKey(source, delta, path, key, levelDiffs, options, isSourceArray, override);
        }

    }

    if (override) {
        // find keys at this level that exists at the source object and delete them
        levelDiffs = this._detectDeletionsAtLevel(source, key, delta, levelDiffs, path, options, isSourceArray);
    }

    if (options.emitEvents) {
        this.emitForPath(path || "*", levelDiffs);
    }

    return levelDiffs;
};

MergeDiff.prototype._mergeAtKey = function (source, delta, path, key, levelDiffs, options, isSourceArray, override) {

    var childDiffs,
        mergingValue,
        existingValue,
        srcKey,
        mergedValue, // this value is what goes out to the tracker its not allways the same as mergingValue
        isExistingValueArray,
        isObjectMerge = false;

    mergingValue = delta[key];

    if (isSourceArray) {
        srcKey = Number(key) + levelDiffs.arrayOffset;
    } else {
        srcKey = key;
    }

    if (_.isFunction(mergingValue)) {
        mergedValue = utils.SERIALIZABLE_FUNCTION;
    } else {
        isObjectMerge = _.isObject(mergingValue);
        if (_.isUndefined(mergingValue)) {
            mergedValue = options.undefinedKeyword;
        } else if (mergingValue === options.undefinedKeyword) {
            mergedValue = mergingValue;
            mergingValue = undefined;
        } else {
            mergedValue = mergingValue;
        }
    }

    // new
    if (!source.hasOwnProperty(srcKey)) {
        if (options.mergeAdditions && delta[key] !== options.deleteKeyword) {

            // add new object
            if (isObjectMerge) {

                source[srcKey] = Object.create(this.getPrototypeOf(mergingValue));
                childDiffs = this._mergeObject(source[srcKey],
                                               mergingValue,
                                               utils.concatPath(path, key),
                                               options,
                                               _.isArray(source[srcKey]),
                                               override);

                levelDiffs.addChildTracking(childDiffs, key);

            // add new primitive
            } else {

                source[srcKey] = mergingValue;

                levelDiffs.hasAdditions = true;
                levelDiffs.additions[key] = mergedValue;
                levelDiffs.hasDifferences = true;
                levelDiffs.differences[key] = mergedValue;
            }
        }
    // existing
    } else {

        existingValue = source[srcKey];
        isExistingValueArray = _.isArray(existingValue);

        // delete
        if (delta[key] === options.deleteKeyword) {

            levelDiffs = this._deleteAtKey(source, path, key, srcKey, options, existingValue, isSourceArray, levelDiffs);

        // update
        } else {

            // update object
            if (isObjectMerge) {

                // we should replace the source value, todo: array merges check is not sufficient
                if (!isExistingValueArray && !utils.hasSamePrototype(existingValue, mergingValue)) {

                    // this is a restructure
                    // handle prototypes
                    source[srcKey] = Object.create(this.getPrototypeOf(mergingValue));

                }

                childDiffs = this._mergeObject(source[srcKey],
                                               mergingValue,
                                               utils.concatPath(path, key),
                                               options,
                                               isExistingValueArray,
                                               override);

                levelDiffs.addChildTracking(childDiffs, key);

            // update primitive
            } else {

                source[srcKey] = mergingValue;

                levelDiffs.hasUpdates = true;
                levelDiffs.updates[key] = {'old': existingValue, 'new': mergedValue};
                levelDiffs.hasDifferences = true;
                levelDiffs.differences[key] = mergedValue;
            }

        }

    }

    return levelDiffs;
};

MergeDiff.prototype._deleteAtKey = function (source, path, key, srcKey, options, existingValue, isSourceArray, levelDiffs) {
    if (options.mergeDeletions) {
        if (isSourceArray) {
            source.splice(srcKey, 1);
            levelDiffs.arrayOffset--;
        } else {
            delete source[srcKey];
        }

    }

    levelDiffs.deletions[key] = existingValue;
    levelDiffs.differences[key] = options.deleteKeyword;
    levelDiffs.hasDeletions = true;

    this.emitInnerDeletions(path, existingValue);

    return levelDiffs;
};

MergeDiff.prototype._detectDeletionsAtLevel = function (source, key, delta, levelDiffs, path, options, isSourceArray) {
    var keys = _.keys(source),
        length = keys.length,
        existingValue,
        srcKey,
        i;

    // run source object at this level
    for (i = 0; i < length; i++) {
        key = keys[i];
        if (isSourceArray) {
            srcKey = Number(key) + levelDiffs.arrayOffset;
        } else {
            srcKey = key;
        }

        if (!delta.hasOwnProperty(key)) {
            existingValue = source[key];

            levelDiffs = this._deleteAtKey(source, path, key, srcKey, options, existingValue,
                isSourceArray, levelDiffs);

        }

    }
    return levelDiffs;
};

MergeDiff.prototype.getPrototypeOf = function (object) {
    return Object.getPrototypeOf(object);
};

MergeDiff.prototype.emitForPath = function (path, tracker) {

    if (tracker.hasAdditions && this.options.emitAdditions) {
        this.additions.emit(path, tracker.additions);
    }

    if (tracker.hasDeletions && this.options.emitAdditions) {
        this.deletions.emit(path, tracker.deletions);
    }

    if (tracker.hasUpdates && this.options.emitAdditions) {
        this.updates.emit(path, tracker.updates);
    }

    if (tracker.hasDifferences && this.options.emitAdditions) {
        console.log("emitting to ", path);
        this.differences.emit(path, tracker.differences);
    }

};

MergeDiff.prototype.emitInnerDeletions = function (deletedObject, path) {
    var keys, key, i;

    if (!_.isObject(deletedObject)) {
        return;
    }

    keys = _.keys(deletedObject);
    for (i = 0; i < keys.length; i++) {
        key = keys[i];
        if (_.isObject(deletedObject[key])) {
            this.emitInnerDeletions(deletedObject[key], utils.concatPath(path, key));
        }
    }

    this.deletions.emit(path, deletedObject);
    this.difference.emit(path, this.options.deleteKeyword);
};

module.exports = MergeDiff;<|endoftext|>


<commit_before>'use strict';

var format = require('./formatter').format;

/**
 * Middleware to format resources into JSON SCHEMA
 *
 * @param {string} options.domain the url request domain
 * @param {string} options.name the name of the resource to format
 */
module.exports = function (options) {
  return function (req, res, next) {
    res.setHeader('Content-type', 'application/json');
    res.send(format({
      url: options.domain + req.url,
      name: options.name,
      body: res.body,
      metadata: res.metadata
    }));
  };
};
<commit_msg>added content-type to middleware<commit_after>'use strict';

var format = require('./formatter').format;

/**
 * Middleware to format resources into JSON SCHEMA
 *
 * @param {string} options.domain the url request domain
 * @param {string} options.name the name of the resource to format
 */
module.exports = function (options) {
  return function (req, res) {
    res.setHeader('Content-Type', 'application/json');
    res.send(format({
      url: options.domain + req.url,
      name: options.name,
      body: res.body,
      metadata: res.metadata
    }));
  };
};
<|endoftext|>


<commit_before>/**
  Resource Middleware
  @description main definition of middleware
  @exports @default {function} remoteResourceMiddleware
**/

require('core-js/library/fn/object/assign');
import RemoteResource from './RemoteResource';
import callRemoteResource from './remoteCall';
import { CallProcessingError } from './errors';
import {
  buildHeaders, parseBody, makeStatusActions, makeRemoteCallHooks, cacheLookup
} from './requestBuilders';

/**
  @name remoteResourceMiddleware
  @desc provides a function that can be used to configure the middleware's
    behavior, returning valid Redux middleware
  @param {object} conf
  @param {object} conf.injectedHeaders headers to be injected on every outgoing req
  @param {object} conf.statusActions HTTP Status codes w/ corresponding types
    or functions that will be emitted when the status code is received
  @returns {function} ReduxMiddleware
**/
export default function remoteResourceMiddleware(_conf) {
  const conf = Object.assign({
    injectedHeaders: {},
    statusActions: {},
    requestOpts: {}
  }, _conf || {});

  // TODO: this whole function is a mess, clean it up
  return store => next => action => {
    // if we don't find our symbol don't even bother
    if (action && action.hasOwnProperty && !action.hasOwnProperty(RemoteResource))
      return next(action);

    const defaultFailure = error => next({
      type: '@@REMOTE_RESOURCE_ERROR', error
    });

    const callOpts = Object.assign({
      method: 'GET',
      headers: {},
      body: null,
      cacheMapping: () => false,
      nocache: false,
      bypassStatusActions: false,
      emitSuccessOnCacheHit: false,
      requestOpts: {}
    }, action[RemoteResource]);

    callOpts.lifecycle = Object.assign({
      request: () => null,
      success: () => null,
      failure: defaultFailure
    }, action[RemoteResource].lifecycle || {});

    function reportError(error) {
      const failAction = callOpts.lifecycle.failure;

      if (typeof failAction === 'function') failAction(error, next);
      else if (typeof failAction === 'object')
        next(Object.assign({}, failAction, { error }));
      else next({ type: failAction, error });
    }

    // ensure we have the required keys
    if (!callOpts.uri) return reportError(new CallProcessingError(
      'Must include URI', 'argValidation'
    ));

    const requestBuilders = [
      cacheLookup(store.getState(), callOpts),
      buildHeaders(store.getState(), callOpts, conf.injectedHeaders),
      parseBody(store.getState(), callOpts.body),
      makeStatusActions(conf.statusActions, next),
      makeRemoteCallHooks(callOpts.lifecycle, next)
    ];

    return Promise.all(requestBuilders)
    .then(([ cachedValue, headers, body, statusActions, hooks ]) => {
      // if we've found a cached value then immediately trigger success
      // TODO: might some clients stop working if suddenly the success hook
      // doesn't receive a response object?
      if (cachedValue) return hooks.onCallSuccess(cachedValue);

      const request = {
        headers, body,
        uri: callOpts.uri,
        method: callOpts.method.toUpperCase(),
        requestOpts: Object.assign({}, conf.requestOpts, callOpts.requestOpts)
      };

      // allow users to supply a custom fetch object
      const custFetch = callOpts._fetch || conf._fetch || null;

      return callRemoteResource(request, statusActions, hooks, custFetch);
    })
    .catch(reportError);
  };
}
<commit_msg>Add note on looking for our symbol<commit_after>/**
  Resource Middleware
  @description main definition of middleware
  @exports @default {function} remoteResourceMiddleware
**/

require('core-js/library/fn/object/assign');
import RemoteResource from './RemoteResource';
import callRemoteResource from './remoteCall';
import { CallProcessingError } from './errors';
import {
  buildHeaders, parseBody, makeStatusActions, makeRemoteCallHooks, cacheLookup
} from './requestBuilders';

/**
  @name remoteResourceMiddleware
  @desc provides a function that can be used to configure the middleware's
    behavior, returning valid Redux middleware
  @param {object} conf
  @param {object} conf.injectedHeaders headers to be injected on every outgoing req
  @param {object} conf.statusActions HTTP Status codes w/ corresponding types
    or functions that will be emitted when the status code is received
  @returns {function} ReduxMiddleware
**/
export default function remoteResourceMiddleware(_conf) {
  const conf = Object.assign({
    injectedHeaders: {},
    statusActions: {},
    requestOpts: {}
  }, _conf || {});

  // TODO: this whole function is a mess, clean it up
  return store => next => action => {
    // make sure we find our Symbol
    // TODO: don't rely on hasOwnProperty (but use it if we can)
    if (action && action.hasOwnProperty && !action.hasOwnProperty(RemoteResource))
      return next(action);

    const defaultFailure = error => next({
      type: '@@REMOTE_RESOURCE_ERROR', error
    });

    const callOpts = Object.assign({
      method: 'GET',
      headers: {},
      body: null,
      cacheMapping: () => false,
      nocache: false,
      bypassStatusActions: false,
      emitSuccessOnCacheHit: false,
      requestOpts: {}
    }, action[RemoteResource]);

    callOpts.lifecycle = Object.assign({
      request: () => null,
      success: () => null,
      failure: defaultFailure
    }, action[RemoteResource].lifecycle || {});

    function reportError(error) {
      const failAction = callOpts.lifecycle.failure;

      if (typeof failAction === 'function') failAction(error, next);
      else if (typeof failAction === 'object')
        next(Object.assign({}, failAction, { error }));
      else next({ type: failAction, error });
    }

    // ensure we have the required keys
    if (!callOpts.uri) return reportError(new CallProcessingError(
      'Must include URI', 'argValidation'
    ));

    const requestBuilders = [
      cacheLookup(store.getState(), callOpts),
      buildHeaders(store.getState(), callOpts, conf.injectedHeaders),
      parseBody(store.getState(), callOpts.body),
      makeStatusActions(conf.statusActions, next),
      makeRemoteCallHooks(callOpts.lifecycle, next)
    ];

    return Promise.all(requestBuilders)
    .then(([ cachedValue, headers, body, statusActions, hooks ]) => {
      // if we've found a cached value then immediately trigger success
      // TODO: might some clients stop working if suddenly the success hook
      // doesn't receive a response object?
      if (cachedValue) return hooks.onCallSuccess(cachedValue);

      const request = {
        headers, body,
        uri: callOpts.uri,
        method: callOpts.method.toUpperCase(),
        requestOpts: Object.assign({}, conf.requestOpts, callOpts.requestOpts)
      };

      // allow users to supply a custom fetch object
      const custFetch = callOpts._fetch || conf._fetch || null;

      return callRemoteResource(request, statusActions, hooks, custFetch);
    })
    .catch(reportError);
  };
}
<|endoftext|>


<commit_before>/**
  Resource Middleware
  @description main definition of middleware
  @exports @default {function} remoteResourceMiddleware
  @exports {function} cacheLookup
  @exports {function} buildHeaders
  @exports {function} parseBody
**/

/* eslint no-unused-vars:0 */
require('core-js/library/fn/object/assign');
import fetch from 'isomorphic-fetch';
import RemoteResource from './RemoteResource';
import { APIError, CallProcessingError } from './errors';
import * as utils from './utils';

/**
  @name remoteResourceMiddleware
  @desc provides a function that can be used to configure the middleware's
    behavior, returning valid Redux middleware
  @param {object} conf
  @param {object} injectedHeaders headers to be injected on every outgoing req
  @param {object} statusActions HTTP Status codes w/ corresponding types
    or functions that will be emitted when the status code is received
  @returns {function} ReduxMiddleware
**/
export default function remoteResourceMiddleware(_conf) {
  const conf = Object.assign({
    injectedHeaders: {},
    statusActions: {}
  }, _conf || {});

  return store => next => action => {
    // if we don't find our symbol don't even bother
    if (action.hasOwnProperty && !action.hasOwnProperty(RemoteResource))
      return next(action);

    const callOpts = action[RemoteResource];

    const validationPipeline = [
      cacheLookup(store.getState(), callOpts),
      buildHeaders(store.getState(), callOpts, conf),
      parseBody(store.getState(), callOpts.body)
    ];

    return Promise.all(validationPipeline)

    // TODO: fill in w/ lifecycle method calls & HTTP request
    .then(() => console.log("We're validated!"))

    // TODO: obviously this is temporary,
    // dispatch the error lifecycle w/ the error
    .catch(error => { throw error; });
  };
}

/**
  @private
  @name cacheLookup
  @desc consults the given state cache mapping to see if we already have a value
    note that this will *not* run when using actionable HTTP methods like POST
  @param {string} method HTTP verb, used to determine if we should cache
  @param {function} cacheMapping
  @param {boolean} nocache
  @param {function} emitSuccessOnCacheHit
  @returns {Promise<boolean>} result

  @TODO allow `cacheMapping` to return a value that will be supplied to the
    success event if `emitSuccessOnCacheHit` is true after the whole thing
    short circuits
**/
export function cacheLookup(state, { method, cacheMapping, nocache }) {
  const stepName = '#cacheLookup';
  if (!utils.isCacheableRequest(method) || !cacheMapping || nocache)
    return Promise.resolve(false);

  try {
    return Promise.resolve(cacheMapping(state));
  } catch (error) {
    const msg = 'Error thrown while evaluating cache mapping';
    return Promise.reject(new CallProcessingError(msg, stepName, error));
  }
}

/**
  @private
  @name buildHeaders
  @desc builds a flat header map by evaluating each k/v in the given headers
    object, optionally injecting any headers from the top level configuration,
    will evaluate promises & functions passing in the state.
    If a header val is a function that returns false the header will be skipped
    NOTE: headers given in the action *will* overwrite globally injected headers
  @param {object} state
  @param {object} headers flat map w/ a mix of values, functions, or promises
  @param {object|string} body used to determine if we'll be sending JSON
  @param {object} injectedHeaders optional
  @returns {Promise<object|CallProcessingError}
**/
export function buildHeaders(state, { headers, body }, { injectedHeaders }) {
  const stepName = '#buildHeaders';
  let headerMap = Object.assign({}, injectedHeaders || {}, actionHeaders);

  if (
    body &&
    typeof body === 'object' &&
    !headerMap.hasOwnProperty('Content-Type')
  ) headerMap['Content-Type'] = 'application/json';

  let ps = Object.keys(headerMap).map(header => {
    const headerVal = headerMap[header];

    if (typeof headerVal === 'function')
      try {
        return { key: header, value: headerVal(state) };
      } catch (error) {
        const msg = `Error thrown when evaluating header function ${header}`;
        return Promise.reject(new CallProcessingError(msg, stepName, error));
      }
    else if (utils.isPromise(headerVal))
      return headerVal.then(v => ({ key: header, value: v }));
    // if you're not a promise but still an object we don't want you
    else if (typeof headerVal === 'object')
      return Promise.reject(
        new CallProcessingError(`Object given for header ${header}`, stepName)
      );
    // I guess it's just a plain value
    else return { key: header, value: headerVal };
  });

  return Promise.all(ps).then(p => p
    .filter(pair => pair.value !== false)
    .reduce((acc, pair) => { acc[pair.key] = pair.value; return acc; }, {})
  );
}

/**
  @private
  @name parseBody
  @desc detects if the request body is an object, if so will attempt to parse
    it as JSON, otherwise just returning it; if body is a function it will be
    called with the state passed in - supports promises
  @param {object} state
  @param {any} body
  @returns {Promise<string>} parsedBody
**/
export function parseBody(state, body) {
  const stepName = '#parseBody';
  if (utils.isPromise(body))
    return body.then(realBody => parseBody(realBody));
  else if (typeof body === 'object')
    try {
      // TODO: can this even fail?
      return Promise.resolve(JSON.stringify(body));
    } catch (error) {
      return Promise.reject(new CallProcessingError(
        'Error parsing body', stepName, error
      ));
    }
  else if (typeof body === 'function')
    try {
      return Promise.resolve(body(state));
    } catch (error) {
      return Promise.reject(new CallProcessingError(
        'Error occurred while evaluating body function', stepName, error
      ));
    }
  else return Promise.resolve(body);
}
<commit_msg>Pass correct args to recursive call<commit_after>/**
  Resource Middleware
  @description main definition of middleware
  @exports @default {function} remoteResourceMiddleware
  @exports {function} cacheLookup
  @exports {function} buildHeaders
  @exports {function} parseBody
**/

/* eslint no-unused-vars:0 */
require('core-js/library/fn/object/assign');
import fetch from 'isomorphic-fetch';
import RemoteResource from './RemoteResource';
import { APIError, CallProcessingError } from './errors';
import * as utils from './utils';

/**
  @name remoteResourceMiddleware
  @desc provides a function that can be used to configure the middleware's
    behavior, returning valid Redux middleware
  @param {object} conf
  @param {object} injectedHeaders headers to be injected on every outgoing req
  @param {object} statusActions HTTP Status codes w/ corresponding types
    or functions that will be emitted when the status code is received
  @returns {function} ReduxMiddleware
**/
export default function remoteResourceMiddleware(_conf) {
  const conf = Object.assign({
    injectedHeaders: {},
    statusActions: {}
  }, _conf || {});

  return store => next => action => {
    // if we don't find our symbol don't even bother
    if (action.hasOwnProperty && !action.hasOwnProperty(RemoteResource))
      return next(action);

    const callOpts = action[RemoteResource];

    const validationPipeline = [
      cacheLookup(store.getState(), callOpts),
      buildHeaders(store.getState(), callOpts, conf),
      parseBody(store.getState(), callOpts.body)
    ];

    return Promise.all(validationPipeline)

    // TODO: fill in w/ lifecycle method calls & HTTP request
    .then(() => console.log("We're validated!"))

    // TODO: obviously this is temporary,
    // dispatch the error lifecycle w/ the error
    .catch(error => { throw error; });
  };
}

/**
  @private
  @name cacheLookup
  @desc consults the given state cache mapping to see if we already have a value
    note that this will *not* run when using actionable HTTP methods like POST
  @param {string} method HTTP verb, used to determine if we should cache
  @param {function} cacheMapping
  @param {boolean} nocache
  @param {function} emitSuccessOnCacheHit
  @returns {Promise<boolean>} result

  @TODO allow `cacheMapping` to return a value that will be supplied to the
    success event if `emitSuccessOnCacheHit` is true after the whole thing
    short circuits
**/
export function cacheLookup(state, { method, cacheMapping, nocache }) {
  const stepName = '#cacheLookup';
  if (!utils.isCacheableRequest(method) || !cacheMapping || nocache)
    return Promise.resolve(false);

  try {
    return Promise.resolve(cacheMapping(state));
  } catch (error) {
    const msg = 'Error thrown while evaluating cache mapping';
    return Promise.reject(new CallProcessingError(msg, stepName, error));
  }
}

/**
  @private
  @name buildHeaders
  @desc builds a flat header map by evaluating each k/v in the given headers
    object, optionally injecting any headers from the top level configuration,
    will evaluate promises & functions passing in the state.
    If a header val is a function that returns false the header will be skipped
    NOTE: headers given in the action *will* overwrite globally injected headers
  @param {object} state
  @param {object} headers flat map w/ a mix of values, functions, or promises
  @param {object|string} body used to determine if we'll be sending JSON
  @param {object} injectedHeaders optional
  @returns {Promise<object|CallProcessingError}
**/
export function buildHeaders(state, { headers, body }, { injectedHeaders }) {
  const stepName = '#buildHeaders';
  let headerMap = Object.assign({}, injectedHeaders || {}, actionHeaders);

  if (
    body &&
    typeof body === 'object' &&
    !headerMap.hasOwnProperty('Content-Type')
  ) headerMap['Content-Type'] = 'application/json';

  let ps = Object.keys(headerMap).map(header => {
    const headerVal = headerMap[header];

    if (typeof headerVal === 'function')
      try {
        return { key: header, value: headerVal(state) };
      } catch (error) {
        const msg = `Error thrown when evaluating header function ${header}`;
        return Promise.reject(new CallProcessingError(msg, stepName, error));
      }
    else if (utils.isPromise(headerVal))
      return headerVal.then(v => ({ key: header, value: v }));
    // if you're not a promise but still an object we don't want you
    else if (typeof headerVal === 'object')
      return Promise.reject(
        new CallProcessingError(`Object given for header ${header}`, stepName)
      );
    // I guess it's just a plain value
    else return { key: header, value: headerVal };
  });

  return Promise.all(ps).then(p => p
    .filter(pair => pair.value !== false)
    .reduce((acc, pair) => { acc[pair.key] = pair.value; return acc; }, {})
  );
}

/**
  @private
  @name parseBody
  @desc detects if the request body is an object, if so will attempt to parse
    it as JSON, otherwise just returning it; if body is a function it will be
    called with the state passed in - supports promises
  @param {object} state
  @param {any} body
  @returns {Promise<string>} parsedBody
**/
export function parseBody(state, body) {
  const stepName = '#parseBody';
  if (utils.isPromise(body))
    return body.then(realBody => parseBody(state, realBody));
  else if (typeof body === 'object')
    try {
      return Promise.resolve(JSON.stringify(body));
    } catch (error) {
      return Promise.reject(new CallProcessingError(
        'Error parsing body', stepName, error
      ));
    }
  else if (typeof body === 'function')
    try {
      return Promise.resolve(body(state));
    } catch (error) {
      return Promise.reject(new CallProcessingError(
        'Error occurred while evaluating body function', stepName, error
      ));
    }
  else return Promise.resolve(body);
}
<|endoftext|>


<commit_before>/**
 * A store of middleware;
 *
 * @module telekit/middleware
 * @author Denis Maslennikov <mrdenniska@gmail.com> (nofach.com)
 * @license MIT
 */

/** Dependencies */
const isArrow = require('isarrow');
const isClass = require('isclass');
const debug = require('debug')('telekit:middleware');

/** Implementation */
class Middleware {
    constructor(kit) {
        this.kit = kit;
        this.store = [];
    }

    transmit(type, ...params) {
        let next = true;
        let fn = () => next = true;

        for (let i = 0, c = this.store.length; i < c; i++) {
            if (!next) break;

            if (this.store[i] && this.store[i][type]) {
                next = false;
                this.store[i][type](...params, fn);
                continue;
            }

            next = true;
        }
    }

    /**
     * Add your middleware to stack of middlewares
     *
     * @param  {Class|Function} value - The middleware
     * @return {Middleware}
     */
    use(value, ...others) {
        let handle = value;

        /** Value is array */
        if (Array.isArray(handle)) {
            handle.forEach((item) => this.use(item, ...others));
            return this;
        }

        debug('use %s', handle.name || '_');

        /** Value is function */
        if (typeof(value) == 'function') {
            /** Value is Class */
            if (isClass(value) && !isArrow(value)) {
                handle = new handle(this.kit, ...others);
            } else {
                handle = handle(this.kit, ...others);
            }
        }

        /** Add handle to stack */
        this.store.push(handle);
        return this;
    }
}

/** Methods */
module.exports = Middleware;
<commit_msg>Changed: if `handle` returns `false`, then we don't add it into stack of middlewares.<commit_after>/**
 * A store of middleware;
 *
 * @module telekit/middleware
 * @author Denis Maslennikov <mrdenniska@gmail.com> (nofach.com)
 * @license MIT
 */

/** Dependencies */
const isArrow = require('isarrow');
const isClass = require('isclass');
const debug = require('debug')('telekit:middleware');

/** Implementation */
class Middleware {
    constructor(kit) {
        this.kit = kit;
        this.store = [];
    }

    transmit(type, ...params) {
        let next = true;
        let fn = () => next = true;

        for (let i = 0, c = this.store.length; i < c; i++) {
            if (!next) break;

            if (this.store[i] && this.store[i][type]) {
                next = false;
                this.store[i][type](...params, fn);
                continue;
            }

            next = true;
        }
    }

    /**
     * Add your middleware to stack of middlewares
     *
     * @param  {Class|Function} value - The middleware
     * @return {Middleware}
     */
    use(value, ...others) {
        let handle = value;

        /** Value is array */
        if (Array.isArray(handle)) {
            handle.forEach((item) => this.use(item, ...others));
            return this;
        }

        debug('use %s', handle.name || '_');

        /** Value is function */
        if (typeof(value) == 'function') {
            /** Value is Class */
            if (isClass(value) && !isArrow(value)) {
                handle = new handle(this.kit, ...others);
            } else {
                handle = handle(this.kit, ...others);
            }
        }

        /** Add handle into stack if it there's */
        if (handle) {
            debug('added %s into stack', handle.name || '_');
            this.store.push(handle);
        }

        return this;
    }
}

/** Methods */
module.exports = Middleware;
<|endoftext|>


<commit_before>/**
 * Extend module's NODE_PATH
 * HACK: temporary solution
 */

require('node-path')(module);

/**
 * Module dependencies.
 */

var mongoose = require('mongoose');
var validators = require('mongoose-validators')
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var log = require('debug')('democracyos:models:law');
var xss = require('xss');

/**
 * Allow 'style' attribute
 */
Object.keys(xss.whiteList).forEach(function(k){
  xss.whiteList[k].push('style');
});

/**
 * Law Vote Schema
 */

var Vote = new Schema({
    author: { type: ObjectId, ref: 'User', required: true }
  , value: { type: String, enum: ['positive', 'negative', 'neutral'], required: true }
  , trustee: { type: ObjectId, ref: 'User' }
  , caster: { type: ObjectId, ref: 'User' }
  , createdAt: { type: Date, default: Date.now }
  , democracy: { type: ObjectId, ref: 'Deployment' }
});

/**
 * Clause Schema
 */

var ClauseSchema = new Schema({
    clauseName: { type: String }
  , order: { type: Number, required: true, default: 0 }
  , text: { type: String, required: true, default: 'Undefined text' }
  , centered: { type: Boolean, required: true, default: false }
});

ClauseSchema.methods.update = function update(data) {
  data = data || {};
  this.clauseName = data.clauseName || this.clauseName;
  this.order = data.order || this.order;
  this.text = xss(data.text || this.text);
};

mongoose.model('Clause', ClauseSchema);

/**
 * Link
 */

var LinkSchema = new Schema({
    text: { type: String }
  , url: { type: String }
});

mongoose.model('Link', LinkSchema);

LinkSchema.methods.update = function update(data) {
  data = data || {};
  this.text = xss(data.text || this.text);
  this.url = data.url || this.url;
};

/**
 * Law Schema
 */

var LawSchema = new Schema({
    state: { type: String, enum: ['bill', 'act', 'project'], default: 'bill', required: true }
  //, status: { type: String, enum: ['open', 'recount', 'closed'], default: 'open', required: true }
  , lawId: { type: String }
  , tag: { type:ObjectId, ref: 'Tag', required: true }
  , officialTitle: { type: String, required: false }
  , mediaTitle: { type: String, required: false }
  , author: { type: String }
  , authorUrl: { type: String }
  , source: { type: String }
  , summary: { type: String, required: false }
  , clauses: [ClauseSchema]
  , votes: [Vote]
  , participants: [{type: ObjectId, ref: 'User' }]
  , createdAt: { type: Date, default: Date.now }
  , updatedAt: { type: Date }
  , closingAt: { type: Date }
  , publishedAt: { type: Date }
  , deletedAt: { type: Date }
  , votable: { type: Boolean, required: true, default: true }
  , clauseTruncationText: { type: String }
  , links: [LinkSchema]
});

/**
 * Define Schema Indexes for MongoDB
 */

LawSchema.index({ createdAt: -1 });
LawSchema.index({ closingAt: -1 });
LawSchema.index({ participants: -1 });
LawSchema.index({ tag: -1 });
LawSchema.index({ lawId: -1 });

/**
 * Make Schema `.toObject()` and
 * `.toJSON()` parse getters for
 * proper JSON API response
 */

LawSchema.set('toObject', { getters: true });
LawSchema.set('toJSON', { getters: true });

LawSchema.options.toObject.transform =
LawSchema.options.toJSON.transform = function(doc, ret) {
  if (ret.votes) delete ret.votes;
  if (ret.upvotes) ret.upvotes = ret.upvotes.map(function(v) { return v.author });
  if (ret.downvotes) ret.downvotes = ret.downvotes.map(function(v) { return v.author });
  if (ret.abstentions) ret.abstentions = ret.abstentions.map(function(v) { return v.author });
}

/**
 * -- Model's event hooks
 */

/**
 * Pre update modified time
 *
 * @api private
 */

LawSchema.pre('save', function(next) {
  this.updatedAt = this.isNew ? this.createdAt : Date.now();
  this.summary = xss(this.summary);
  next();
});

/**
 * -- Model's API extension
 */

/**
 * Compile lawId to generate
 * a human readable title
 *
 * @return {String} clauses
 * @api public
 */

LawSchema.virtual('title').get(function() {
  return this.lawId;
});

/**
 * Compile clauses to render
 * text content
 *
 * @return {String} clauses
 * @api public
 */

LawSchema.virtual('content').get(function() {
  if (!this.clauses) return;
  return this.clauses.sort(function(a, b) {
    var sort = a.order - b.order;
    sort = sort > 0 ? 1 : -1;
    return sort;
  }).map(function(c) {
    if (c.text) return (c.clauseName ? c.clauseName + ': ' : '') + c.text;
  }).join('\n');
});

/**
 * Get `positive` votes
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('upvotes').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'positive' === v.value;
  });
});

/**
 * Get `negative` votes
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('downvotes').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'negative' === v.value;
  });
});

/**
 * Get `neutral` votes
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('abstentions').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'neutral' === v.value;
  });
});

/**
 * Get law `status`
 *
 * @return {String} status
 * @api public
 */

LawSchema.virtual('status').get(function() {
  if (!this.closingAt) return 'open';

  return this.closingAt.getTime() < Date.now()
    ? 'closed'
    : 'open';
});

/**
 * Wether the `law` is open
 *
 * @return {Boolean} open
 * @api public
 */

LawSchema.virtual('open').get(function() {
  return 'open' === this.status;
});

/**
 * Wether the `law` is closed
 *
 * @return {Boolean} closed
 * @api public
 */

LawSchema.virtual('closed').get(function() {
  return 'closed' === this.status;
});

/**
 * Wether the `law` was deleted
 *
 * @return {Boolean} deleted
 * @api public
 */

LawSchema.virtual('deleted').get(function() {
  return !!this.deletedAt;
});

/**
 * Wether the `law` is public
 *
 * @return {Boolean} public
 * @api public
 */

LawSchema.virtual('public').get(function() {
  return !!this.publishedAt;
});

/**
 * Wether the `law` is draft
 *
 * @return {Boolean} draft
 * @api public
 */

LawSchema.virtual('draft').get(function() {
  return !this.publishedAt;
});

/**
 * Vote Law with provided user
 * and voting value
 *
 * @param {User|ObjectId|String} user
 * @param {String} value
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.vote = function(user, value, cb) {
  if ('recount' === this.status) return cb(new Error('Voting is closed on recount.'));
  if ('closed' === this.status) return cb(new Error('Voting is closed.'));
  // Here we could provide a 5000ms tolerance (5s)
  // or something... to prevent false positives
  if (this.closingAt && (+new Date(this.closingAt) < +new Date) ) return cb(new Error('Can\'t vote after closing date.'));

  var vote = { author: user, value: value, caster: user };

  this.unvote(user, onunvote.bind(this));

  function onunvote(err) {
    if (err) {
      if ('function' === typeof cb) return cb(err);
      else throw err;
    }

    this.votes.push(vote);

    // Add user as participant
    this.addParticipant(user);
    if ('function' === typeof cb) this.save(cb);
  }
};

/**
 * Add participant to law
 *
 * @param {User|ObjectId|String} user
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.addParticipant = function(user, cb) {
  this.participants.addToSet(user);
  if (cb) this.save(cb);
}

/**
 * Unvote Law from provided user
 *
 * @param {User|ObjectId|String} user
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.unvote = function(user, cb) {
  var votes = this.votes;
  var c = user.get ? user.get('_id') : user;

  var voted = votes.filter(function(v) {
    var a = v.author.get ? v.author.get('_id') : v.author;
    return a.equals
      ? a.equals(c)
      : a === c;
  });

  log('About to remove votes %j', voted);
  voted.length && voted.forEach(function(v) {
    var removed = votes.id(v.id).remove();
    log('Remove vote %j', removed);
  });

  if ('function' === typeof cb) this.save(cb);
};

/**
 * Check for vote status of user
 *
 * @param {User|ObjectId|String} user
 * @api public
 */

LawSchema.methods.votedBy = function(user) {
  var votes = this.votes;
  var c = user.get ? user.get('_id') : user;

  var voted = votes.filter(function(v) {
    var a = v.author.get ? v.author.get('_id') : v.author;
    return a.equals
      ? a.equals(c)
      : a === c;
  });

  return 1 === voted.length;
};
/**
 * Close law to prevent future vote casts
 *
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.close = function(cb) {
  if (+new Date(this.closingAt) < +new Date) {
    log('Deny to close law before closing date.');
    return cb(new Error('Deny to close law before closing date.'));
  }
  this.status = 'closed';
  cb && this.save(cb);
}

/**
 * Flag status as `recount` to prevent vote casts
 *
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.recount = function(cb) {
  if (+new Date(this.closingAt) < +new Date) {
    log('Deny to recount law before closing date.');
    return cb && cb(new Error('Deny to recount law before closing date.'));
  }
  this.status = 'recount';
  cb && this.save(cb);
}

module.exports = function initialize(conn) {
  return conn.model('Law', LawSchema);
};
<commit_msg>Revert "[models:law] Removed URL validation in model-tier #834 #840"<commit_after>/**
 * Extend module's NODE_PATH
 * HACK: temporary solution
 */

require('node-path')(module);

/**
 * Module dependencies.
 */

var mongoose = require('mongoose');
var validators = require('mongoose-validators')
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var log = require('debug')('democracyos:models:law');
var xss = require('xss');

/**
 * Allow 'style' attribute
 */
Object.keys(xss.whiteList).forEach(function(k){
  xss.whiteList[k].push('style');
});

/**
 * Law Vote Schema
 */

var Vote = new Schema({
    author: { type: ObjectId, ref: 'User', required: true }
  , value: { type: String, enum: ['positive', 'negative', 'neutral'], required: true }
  , trustee: { type: ObjectId, ref: 'User' }
  , caster: { type: ObjectId, ref: 'User' }
  , createdAt: { type: Date, default: Date.now }
  , democracy: { type: ObjectId, ref: 'Deployment' }
});

/**
 * Clause Schema
 */

var ClauseSchema = new Schema({
    clauseName: { type: String }
  , order: { type: Number, required: true, default: 0 }
  , text: { type: String, required: true, default: 'Undefined text' }
  , centered: { type: Boolean, required: true, default: false }
});

ClauseSchema.methods.update = function update(data) {
  data = data || {};
  this.clauseName = data.clauseName || this.clauseName;
  this.order = data.order || this.order;
  this.text = xss(data.text || this.text);
};

mongoose.model('Clause', ClauseSchema);

/**
 * Link
 */

var LinkSchema = new Schema({
    text: { type: String }
  , url: { type: String, validate: validators.isURL({ skipEmpty: true }) }
});

mongoose.model('Link', LinkSchema);

LinkSchema.methods.update = function update(data) {
  data = data || {};
  this.text = xss(data.text || this.text);
  this.url = data.url || this.url;
};

/**
 * Law Schema
 */

var LawSchema = new Schema({
    state: { type: String, enum: ['bill', 'act', 'project'], default: 'bill', required: true }
  //, status: { type: String, enum: ['open', 'recount', 'closed'], default: 'open', required: true }
  , lawId: { type: String }
  , tag: { type:ObjectId, ref: 'Tag', required: true }
  , officialTitle: { type: String, required: false }
  , mediaTitle: { type: String, required: false }
  , author: { type: String }
  , authorUrl: { type: String, validate: validators.isURL({ skipEmpty: true }) }
  , source: { type: String }
  , summary: { type: String, required: false }
  , clauses: [ClauseSchema]
  , votes: [Vote]
  , participants: [{type: ObjectId, ref: 'User' }]
  , createdAt: { type: Date, default: Date.now }
  , updatedAt: { type: Date }
  , closingAt: { type: Date }
  , publishedAt: { type: Date }
  , deletedAt: { type: Date }
  , votable: { type: Boolean, required: true, default: true }
  , clauseTruncationText: { type: String }
  , links: [LinkSchema]
});

/**
 * Define Schema Indexes for MongoDB
 */

LawSchema.index({ createdAt: -1 });
LawSchema.index({ closingAt: -1 });
LawSchema.index({ participants: -1 });
LawSchema.index({ tag: -1 });
LawSchema.index({ lawId: -1 });

/**
 * Make Schema `.toObject()` and
 * `.toJSON()` parse getters for
 * proper JSON API response
 */

LawSchema.set('toObject', { getters: true });
LawSchema.set('toJSON', { getters: true });

LawSchema.options.toObject.transform =
LawSchema.options.toJSON.transform = function(doc, ret) {
  if (ret.votes) delete ret.votes;
  if (ret.upvotes) ret.upvotes = ret.upvotes.map(function(v) { return v.author });
  if (ret.downvotes) ret.downvotes = ret.downvotes.map(function(v) { return v.author });
  if (ret.abstentions) ret.abstentions = ret.abstentions.map(function(v) { return v.author });
}

/**
 * -- Model's event hooks
 */

/**
 * Pre update modified time
 *
 * @api private
 */

LawSchema.pre('save', function(next) {
  this.updatedAt = this.isNew ? this.createdAt : Date.now();
  this.summary = xss(this.summary);
  next();
});

/**
 * -- Model's API extension
 */

/**
 * Compile lawId to generate
 * a human readable title
 *
 * @return {String} clauses
 * @api public
 */

LawSchema.virtual('title').get(function() {
  return this.lawId;
});

/**
 * Compile clauses to render
 * text content
 *
 * @return {String} clauses
 * @api public
 */

LawSchema.virtual('content').get(function() {
  if (!this.clauses) return;
  return this.clauses.sort(function(a, b) {
    var sort = a.order - b.order;
    sort = sort > 0 ? 1 : -1;
    return sort;
  }).map(function(c) {
    if (c.text) return (c.clauseName ? c.clauseName + ': ' : '') + c.text;
  }).join('\n');
});

/**
 * Get `positive` votes
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('upvotes').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'positive' === v.value;
  });
});

/**
 * Get `negative` votes
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('downvotes').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'negative' === v.value;
  });
});

/**
 * Get `neutral` votes
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('abstentions').get(function() {
  if (!this.votes) return;
  return this.votes.filter(function(v) {
    return 'neutral' === v.value;
  });
});

/**
 * Get law `status`
 *
 * @return {String} status
 * @api public
 */

LawSchema.virtual('status').get(function() {
  if (!this.closingAt) return 'open';

  return this.closingAt.getTime() < Date.now()
    ? 'closed'
    : 'open';
});

/**
 * Wether the `law` is open
 *
 * @return {Boolean} open
 * @api public
 */

LawSchema.virtual('open').get(function() {
  return 'open' === this.status;
});

/**
 * Wether the `law` is closed
 *
 * @return {Boolean} closed
 * @api public
 */

LawSchema.virtual('closed').get(function() {
  return 'closed' === this.status;
});

/**
 * Wether the `law` was deleted
 *
 * @return {Boolean} deleted
 * @api public
 */

LawSchema.virtual('deleted').get(function() {
  return !!this.deletedAt;
});

/**
 * Wether the `law` is public
 *
 * @return {Boolean} public
 * @api public
 */

LawSchema.virtual('public').get(function() {
  return !!this.publishedAt;
});

/**
 * Wether the `law` is draft
 *
 * @return {Boolean} draft
 * @api public
 */

LawSchema.virtual('draft').get(function() {
  return !this.publishedAt;
});

/**
 * Vote Law with provided user
 * and voting value
 *
 * @param {User|ObjectId|String} user
 * @param {String} value
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.vote = function(user, value, cb) {
  if ('recount' === this.status) return cb(new Error('Voting is closed on recount.'));
  if ('closed' === this.status) return cb(new Error('Voting is closed.'));
  // Here we could provide a 5000ms tolerance (5s)
  // or something... to prevent false positives
  if (this.closingAt && (+new Date(this.closingAt) < +new Date) ) return cb(new Error('Can\'t vote after closing date.'));

  var vote = { author: user, value: value, caster: user };

  this.unvote(user, onunvote.bind(this));

  function onunvote(err) {
    if (err) {
      if ('function' === typeof cb) return cb(err);
      else throw err;
    }

    this.votes.push(vote);

    // Add user as participant
    this.addParticipant(user);
    if ('function' === typeof cb) this.save(cb);
  }
};

/**
 * Add participant to law
 *
 * @param {User|ObjectId|String} user
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.addParticipant = function(user, cb) {
  this.participants.addToSet(user);
  if (cb) this.save(cb);
}

/**
 * Unvote Law from provided user
 *
 * @param {User|ObjectId|String} user
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.unvote = function(user, cb) {
  var votes = this.votes;
  var c = user.get ? user.get('_id') : user;

  var voted = votes.filter(function(v) {
    var a = v.author.get ? v.author.get('_id') : v.author;
    return a.equals
      ? a.equals(c)
      : a === c;
  });

  log('About to remove votes %j', voted);
  voted.length && voted.forEach(function(v) {
    var removed = votes.id(v.id).remove();
    log('Remove vote %j', removed);
  });

  if ('function' === typeof cb) this.save(cb);
};

/**
 * Check for vote status of user
 *
 * @param {User|ObjectId|String} user
 * @api public
 */

LawSchema.methods.votedBy = function(user) {
  var votes = this.votes;
  var c = user.get ? user.get('_id') : user;

  var voted = votes.filter(function(v) {
    var a = v.author.get ? v.author.get('_id') : v.author;
    return a.equals
      ? a.equals(c)
      : a === c;
  });

  return 1 === voted.length;
};
/**
 * Close law to prevent future vote casts
 *
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.close = function(cb) {
  if (+new Date(this.closingAt) < +new Date) {
    log('Deny to close law before closing date.');
    return cb(new Error('Deny to close law before closing date.'));
  }
  this.status = 'closed';
  cb && this.save(cb);
}

/**
 * Flag status as `recount` to prevent vote casts
 *
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.recount = function(cb) {
  if (+new Date(this.closingAt) < +new Date) {
    log('Deny to recount law before closing date.');
    return cb && cb(new Error('Deny to recount law before closing date.'));
  }
  this.status = 'recount';
  cb && this.save(cb);
}

module.exports = function initialize(conn) {
  return conn.model('Law', LawSchema);
};
<|endoftext|>


<commit_before>/**
 * Extend module's NODE_PATH
 * HACK: temporary solution
 */

var resolve = require('path').resolve;
require(resolve('lib/node-path'))(module);

var t = require('t-component');

/**
 * Module dependencies.
 */

var mongoose = require('mongoose')
  , Schema = mongoose.Schema
  , ObjectId = Schema.ObjectId
  , log = require('debug')('models:law');

/**
 * Law Vote Schema
 */

var Vote = new Schema({
    author: { type: ObjectId, ref: 'Citizen', required: true }
  , value: { type: String, enum: ["positive", "negative", "neutral"], required: true }
  , trustee: { type: ObjectId, ref: 'Citizen' }
  , caster: { type: ObjectId, ref: 'Citizen' }
  , createdAt: { type: Date, default: Date.now }
});

/**
 * Law Schema
 */

var LawSchema = new Schema({
    state: { type: String, enum: ['bill', 'act', 'project'], default: 'bill', required: true }
  , status: { type: String, enum: ['open', 'recount', 'closed'], default: 'open', required: true }
  , lawId: { type: String, required: true }
  , tag: { type:ObjectId, ref:'Tag', required: true }
  , officialTitle: { type: String, required: false }
  , mediaTitle: { type: String, required: false }
  , summary: { type: String, required: false }
  , clauses: [{
        clauseId: { type: String, required: true }
      , order: { type: Number, required: true }
      , text: { type: String, required: true }
    }]
  , votes: [Vote]
  , participants: [{type: ObjectId, ref: 'Citizen' }]
  , createdAt: { type: Date, default: Date.now }
  , updatedAt: { type: Date }
  , closingAt: { type: Date }
});

/**
 * Define Schema Indexes for MongoDB
 */

LawSchema.index({ createdAt: -1 });
LawSchema.index({ participants: -1 });
LawSchema.index({ tag: -1 });
LawSchema.index({ state: 1, lawId: 1 }, { unique: true, dropDups: true });

/**
 * Make Schema `.toObject()` and
 * `.toJSON()` parse getters for
 * proper JSON API response
 */

LawSchema.set('toObject', { getters: true });
LawSchema.set('toJSON', { getters: true });

/**
 * -- Model's event hooks
 */

/**
 * Pre update modified time
 *
 * @api private
 */

LawSchema.pre('save', function(next) {
  this.updatedAt = this.isNew ? this.createdAt : Date.now();
  next();
});

/**
 * -- Model's API extension
 */

/**
 * Compile lawId to generate
 * a human readable title
 *
 * @return {String} clauses
 * @api public
 */

LawSchema.virtual('title').get(function() {
  var title = '';
  if ('project' === this.state) title += (t('Project') + ' ');
  if ('bill' === this.state) title += (t('Bill') + ' ');
  if ('act' === this.state) title += (t('Law') + ' ');
  return title.concat(this.lawId);
});

/**
 * Compile clauses to render
 * text content
 *
 * @return {String} clauses
 * @api public
 */

LawSchema.virtual('content').get(function() {
  return this.clauses.sort(function(a, b) {
    var sort = a.order - b.order;
    sort = sort > 0 ? 1 : -1;
    return sort;
  }).map(function(c) {
    if (c.text) return t('Clause') + ' ' + c.clauseId + ': ' + c.text;
  }).join('\n');
});

/**
 * Get `positive` vots
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('upvotes').get(function() {
  return this.votes.filter(function(v) {
    return "positive" === v.value;
  });
});

/**
 * Get `negative` votes
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('downvotes').get(function() {
  return this.votes.filter(function(v) {
    return "negative" === v.value;
  });
});

/**
 * Get `neutral` votes
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('abstentions').get(function() {
  return this.votes.filter(function(v) {
    return "neutral" === v.value;
  });
});

/**
 * Vote Law with provided citizen
 * and voting value
 *
 * @param {Citizen|ObjectId|String} citizen
 * @param {String} value
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.vote = function(citizen, value, cb) {
  if ('recount' === this.status) return cb(new Error('Voting is closed on recount.'));
  if ('closed' === this.status) return cb(new Error('Voting is closed.'));
  // Here we could provide a 5000ms tolerance (5s)
  // or something... to prevent false positives
  if (this.closingAt && (+new Date(this.closingAt) < +new Date) ) return cb(new Error('Can\'t vote after closing date.'));

  var vote = { author: citizen, value: value, caster: citizen };
  this.unvote(citizen);
  this.votes.push(vote);
  // Add citizen as participant
  this.participants.push(citizen);
  this.save(cb);
};

/**
 * Unvote Law from provided citizen
 *
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.unvote = function(citizen, cb) {
  var votes = this.votes;
  var c = citizen.get ? citizen.get('_id') : citizen;

  var voted = votes.filter(function(v) {
    var a = v.author.get ? v.author.get('_id') : v.author;
    return a.equals
      ? a.equals(c)
      : a === c;
  });

  log('About to remove votes %j', voted);
  voted.length && voted.forEach(function(v) {
    var removed = votes.id(v.id).remove();
    log('Remove vote %j', removed);
  });

  if (cb) this.save(cb);
};

/**
 * Close law to prevent future vote casts
 *
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.close = function(cb) {
  if (+new Date(this.closingAt) < +new Date) {
    log('Deny to close law before closing date.');
    return cb(new Error('Deny to close law before closing date.'));
  };
  this.status = 'closed';
  cb && this.save(cb);
}

/**
 * Flag status as `recount` to prevent vote casts
 *
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.recount = function(cb) {
  if (+new Date(this.closingAt) < +new Date) {
    log('Deny to recount law before closing date.');
    return cb && cb(new Error('Deny to recount law before closing date.'));
  };
  this.status = 'recount';
  cb && this.save(cb);
}

/**
 * Expose Mongoose model loaded
 */

module.exports = mongoose.model('Law', LawSchema);

<commit_msg>[models:law] - Fix non repeating participants. #42<commit_after>/**
 * Extend module's NODE_PATH
 * HACK: temporary solution
 */

var resolve = require('path').resolve;
require(resolve('lib/node-path'))(module);

var t = require('t-component');

/**
 * Module dependencies.
 */

var mongoose = require('mongoose')
  , Schema = mongoose.Schema
  , ObjectId = Schema.ObjectId
  , log = require('debug')('models:law');

/**
 * Law Vote Schema
 */

var Vote = new Schema({
    author: { type: ObjectId, ref: 'Citizen', required: true }
  , value: { type: String, enum: ["positive", "negative", "neutral"], required: true }
  , trustee: { type: ObjectId, ref: 'Citizen' }
  , caster: { type: ObjectId, ref: 'Citizen' }
  , createdAt: { type: Date, default: Date.now }
});

/**
 * Law Schema
 */

var LawSchema = new Schema({
    state: { type: String, enum: ['bill', 'act', 'project'], default: 'bill', required: true }
  , status: { type: String, enum: ['open', 'recount', 'closed'], default: 'open', required: true }
  , lawId: { type: String, required: true }
  , tag: { type:ObjectId, ref:'Tag', required: true }
  , officialTitle: { type: String, required: false }
  , mediaTitle: { type: String, required: false }
  , summary: { type: String, required: false }
  , clauses: [{
        clauseId: { type: String, required: true }
      , order: { type: Number, required: true }
      , text: { type: String, required: true }
    }]
  , votes: [Vote]
  , participants: [{type: ObjectId, ref: 'Citizen' }]
  , createdAt: { type: Date, default: Date.now }
  , updatedAt: { type: Date }
  , closingAt: { type: Date }
});

/**
 * Define Schema Indexes for MongoDB
 */

LawSchema.index({ createdAt: -1 });
LawSchema.index({ participants: -1 });
LawSchema.index({ tag: -1 });
LawSchema.index({ state: 1, lawId: 1 }, { unique: true, dropDups: true });

/**
 * Make Schema `.toObject()` and
 * `.toJSON()` parse getters for
 * proper JSON API response
 */

LawSchema.set('toObject', { getters: true });
LawSchema.set('toJSON', { getters: true });

/**
 * -- Model's event hooks
 */

/**
 * Pre update modified time
 *
 * @api private
 */

LawSchema.pre('save', function(next) {
  this.updatedAt = this.isNew ? this.createdAt : Date.now();
  next();
});

/**
 * -- Model's API extension
 */

/**
 * Compile lawId to generate
 * a human readable title
 *
 * @return {String} clauses
 * @api public
 */

LawSchema.virtual('title').get(function() {
  var title = '';
  if ('project' === this.state) title += (t('Project') + ' ');
  if ('bill' === this.state) title += (t('Bill') + ' ');
  if ('act' === this.state) title += (t('Law') + ' ');
  return title.concat(this.lawId);
});

/**
 * Compile clauses to render
 * text content
 *
 * @return {String} clauses
 * @api public
 */

LawSchema.virtual('content').get(function() {
  return this.clauses.sort(function(a, b) {
    var sort = a.order - b.order;
    sort = sort > 0 ? 1 : -1;
    return sort;
  }).map(function(c) {
    if (c.text) return t('Clause') + ' ' + c.clauseId + ': ' + c.text;
  }).join('\n');
});

/**
 * Get `positive` vots
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('upvotes').get(function() {
  return this.votes.filter(function(v) {
    return "positive" === v.value;
  });
});

/**
 * Get `negative` votes
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('downvotes').get(function() {
  return this.votes.filter(function(v) {
    return "negative" === v.value;
  });
});

/**
 * Get `neutral` votes
 *
 * @return {Array} voters
 * @api public
 */

LawSchema.virtual('abstentions').get(function() {
  return this.votes.filter(function(v) {
    return "neutral" === v.value;
  });
});

/**
 * Vote Law with provided citizen
 * and voting value
 *
 * @param {Citizen|ObjectId|String} citizen
 * @param {String} value
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.vote = function(citizen, value, cb) {
  if ('recount' === this.status) return cb(new Error('Voting is closed on recount.'));
  if ('closed' === this.status) return cb(new Error('Voting is closed.'));
  // Here we could provide a 5000ms tolerance (5s)
  // or something... to prevent false positives
  if (this.closingAt && (+new Date(this.closingAt) < +new Date) ) return cb(new Error('Can\'t vote after closing date.'));

  var vote = { author: citizen, value: value, caster: citizen };
  this.unvote(citizen);
  this.votes.push(vote);
  // Add citizen as participant
  this.addParticipant(citizen);
  this.save(cb);
};

/**
 * Add participant to law
 *
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.addParticipant = function(citizen, cb) {
  this.participants.addToSet(citizen);
  if (cb) this.save(cb);
}

/**
 * Unvote Law from provided citizen
 *
 * @param {Citizen|ObjectId|String} citizen
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.unvote = function(citizen, cb) {
  var votes = this.votes;
  var c = citizen.get ? citizen.get('_id') : citizen;

  var voted = votes.filter(function(v) {
    var a = v.author.get ? v.author.get('_id') : v.author;
    return a.equals
      ? a.equals(c)
      : a === c;
  });

  log('About to remove votes %j', voted);
  voted.length && voted.forEach(function(v) {
    var removed = votes.id(v.id).remove();
    log('Remove vote %j', removed);
  });

  if (cb) this.save(cb);
};

/**
 * Close law to prevent future vote casts
 *
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.close = function(cb) {
  if (+new Date(this.closingAt) < +new Date) {
    log('Deny to close law before closing date.');
    return cb(new Error('Deny to close law before closing date.'));
  };
  this.status = 'closed';
  cb && this.save(cb);
}

/**
 * Flag status as `recount` to prevent vote casts
 *
 * @param {Function} cb
 * @api public
 */

LawSchema.methods.recount = function(cb) {
  if (+new Date(this.closingAt) < +new Date) {
    log('Deny to recount law before closing date.');
    return cb && cb(new Error('Deny to recount law before closing date.'));
  };
  this.status = 'recount';
  cb && this.save(cb);
}

/**
 * Expose Mongoose model loaded
 */

module.exports = mongoose.model('Law', LawSchema);

<|endoftext|>


<commit_before>var Q       = require('q'),
    mpd     = require('mpd'),
    EventEmitter = require('events').EventEmitter,
    winston = require('winston');

exports.create = function (port, logger) {
  var client;

  var logger = logger || winston;
  var ready = Q.defer();

  function connect(mpdClient) {
    client = mpdClient || mpd.connect({
      port: port, host: 'localhost'
    });

    client.on('ready', function() {
      logger.info('ready on port '+port);
      ready.resolve();
    });

    client.on('error', function(err) {
      var errMsg = 'Cannot connect to port '+port+': '+err;

      instance.emit('error', err);
      ready.reject(err);
    });

    client.on('system', function(name) {
      instance.emit('system', name);
    });

    instance.on('error', function(err) {
      logger.warn(err);
    });

    return ready.promise;
  }

  function formatCommand(command) {
    if(command.length == 1) {
      command[1] = [];
    }

    return mpd.cmd(command[0], command[1]);
  }

  function sendCommands(commands) {
    var commandList = commands.map(formatCommand),
        promise     = instance.ready().then(
            Q.ninvoke(client, 'sendCommands', commandList)
        );

    return promise;
  }

  var instance = new EventEmitter();
  instance.formatCommand = formatCommand;
  instance.sendCommands = sendCommands;
  instance.connect = connect;
  instance.ready = function() { return ready.promise} ;

  return instance;
};
<commit_msg>Use a function to chain sendCommands method<commit_after>var Q       = require('q'),
    mpd     = require('mpd'),
    EventEmitter = require('events').EventEmitter,
    winston = require('winston');

exports.create = function (port, logger) {
  var client;

  var logger = logger || winston;
  var ready = Q.defer();

  function connect(mpdClient) {
    client = mpdClient || mpd.connect({
      port: port, host: 'localhost'
    });

    client.on('ready', function() {
      logger.info('ready on port '+port);
      ready.resolve();
    });

    client.on('error', function(err) {
      var errMsg = 'Cannot connect to port '+port+': '+err;

      instance.emit('error', err);
      ready.reject(err);
    });

    client.on('system', function(name) {
      instance.emit('system', name);
    });

    instance.on('error', function(err) {
      logger.warn(err);
    });

    return ready.promise;
  }

  function formatCommand(command) {
    if(command.length == 1) {
      command[1] = [];
    }

    return mpd.cmd(command[0], command[1]);
  }

  function sendCommands(commands) {
    var commandList = commands.map(formatCommand),
        promise     = instance.ready().then(function() {
            Q.ninvoke(client, 'sendCommands', commandList)
        });

    return promise;
  }

  var instance = new EventEmitter();
  instance.formatCommand = formatCommand;
  instance.sendCommands = sendCommands;
  instance.connect = connect;
  instance.ready = function() { return ready.promise} ;

  return instance;
};
<|endoftext|>


<commit_before>var nmea = require('nmea');

exports.create = function (jsonStream, rawStream, nmeaArg) {
  var FILEPREFIX = 'file:';
  var cmdArguments = ['-c', 'gpspipe -r'];
  var options = {};
  if (nmeaArg.indexOf(FILEPREFIX) === 0) {
    cmdArguments = ['-c', 'while true ; do exec < ' + nmeaArg.substr(FILEPREFIX.length, nmeaArg.length) + '; while read line; do echo $line; sleep 1; done; done'];
  }
  rl = require('readline');

  var nmeasource = require('child_process').spawn('sh', cmdArguments);
  linereader = rl.createInterface(nmeasource.stdout, nmeasource.stdin);

  var shouldTransmit = function (nmeaMessage) {
    return typeof nmeaMessage != 'undefined';
  };

  linereader.on('line', function (data) {
    rawStream.push('' + data);
    var nmeaMessage = nmea.parse('' + data);
    if (shouldTransmit(nmeaMessage)) {
      jsonStream.push(nmeaMessage);
    }
  });

  nmeasource.stderr.on('data', function (data) {
    console.log('nmea-err:' + data);
  });

  nmeasource.on('close', function (code) {
    console.log('nmea process exited with code ' + code);
  });

  return nmeasource;
};<commit_msg>Add support for .gz nmea files<commit_after>var nmea = require('nmea');
var zlib = require('zlib');

exports.create = function (jsonStream, rawStream, nmeaArg) {
  var FILEPREFIX = 'file:';
  var cmdArguments = ['-c', 'gpspipe -r'];
  var options = {};
  if (nmeaArg.indexOf(FILEPREFIX) === 0) {
    if (endsWith(nmeaArg, ".gz")) {
      cmdArguments = ['-c', 'while true ; do gzcat < ' + nmeaArg.substr(FILEPREFIX.length, nmeaArg.length) + '| while read line; do echo $line; sleep 0.1; done; done'];
    } else {
      cmdArguments = ['-c', 'while true ; do exec < ' + nmeaArg.substr(FILEPREFIX.length, nmeaArg.length) + '; while read line; do echo $line; sleep 0.1; done; done'];
    }
  }
  rl = require('readline');

  var nmeasource = require('child_process').spawn('sh', cmdArguments);
  linereader = rl.createInterface(nmeasource.stdout, nmeasource.stdin);

  var shouldTransmit = function (nmeaMessage) {
    return typeof nmeaMessage != 'undefined';
  };

  linereader.on('line', function (data) {
    rawStream.push('' + data);
    var nmeaMessage = nmea.parse('' + data);
    if (shouldTransmit(nmeaMessage)) {
      jsonStream.push(nmeaMessage);
    }
  });

  nmeasource.stderr.on('data', function (data) {
    console.log('nmea-err:' + data);
  });

  nmeasource.on('close', function (code) {
    console.log('nmea process exited with code ' + code);
  });

  return nmeasource;
}
;

function endsWith(str, suffix) {
  return str.indexOf(suffix, str.length - suffix.length) !== -1;
}<|endoftext|>


<commit_before>// Generated by CoffeeScript 1.8.0
(function() {
  var EventEmitter, NodeCache, _,
    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
    __hasProp = {}.hasOwnProperty,
    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    __slice = [].slice;

  _ = require("underscore");

  EventEmitter = require('events').EventEmitter;

  module.exports = NodeCache = (function(_super) {
    __extends(NodeCache, _super);

    function NodeCache(options) {
      this.options = options != null ? options : {};
      this._error = __bind(this._error, this);
      this._getValLength = __bind(this._getValLength, this);
      this._getKeyLength = __bind(this._getKeyLength, this);
      this._unwrap = __bind(this._unwrap, this);
      this._wrap = __bind(this._wrap, this);
      this._check = __bind(this._check, this);
      this._checkData = __bind(this._checkData, this);
      this.flushAll = __bind(this.flushAll, this);
      this.getStats = __bind(this.getStats, this);
      this.ttl = __bind(this.ttl, this);
      this.del = __bind(this.del, this);
      this.set = __bind(this.set, this);
      this.get = __bind(this.get, this);
      this.data = {};
      this.options = _.extend({
        forceString: false,
        objectValueSize: 80,
        arrayValueSize: 40,
        stdTTL: 0,
        checkperiod: 600
      }, this.options);
      this.stats = {
        hits: 0,
        misses: 0,
        keys: 0,
        ksize: 0,
        vsize: 0
      };
      this._checkData();
    }

    NodeCache.prototype.get = function(keys, cb) {
      var key, oRet, _i, _len;
      if (_.isString(keys)) {
        keys = [keys];
      }
      oRet = {};
      for (_i = 0, _len = keys.length; _i < _len; _i++) {
        key = keys[_i];
        if ((this.data[key] != null) && this._check(key, this.data[key])) {
          this.stats.hits++;
          oRet[key] = this._unwrap(this.data[key]);
        } else {
          this.stats.misses++;
        }
      }
      if (cb != null) {
        cb(null, oRet);
      }
      return oRet;
    };

    NodeCache.prototype.set = function(key, value, ttl, cb) {
      var existend;
      existend = false;
      if (this.options.forceString && !_.isString(value)) {
        value = JSON.stringify(value);
      }
      if (arguments.length === 3 && _.isFunction(ttl)) {
        cb = ttl;
        ttl = this.options.stdTTL;
      }
      if (this.data[key]) {
        existend = true;
        this.stats.vsize -= this._getValLength(this._unwrap(this.data[key]));
      }
      this.data[key] = this._wrap(value, ttl);
      this.stats.vsize += this._getValLength(value);
      if (!existend) {
        this.stats.ksize += this._getKeyLength(key);
        this.stats.keys++;
      }
      this.emit("set", key, value);
      if (cb != null) {
        cb(null, true);
      }
      return true;
    };

    NodeCache.prototype.del = function(keys, cb) {
      var delCount, key, _i, _len;
      if (_.isString(keys)) {
        keys = [keys];
      }
      delCount = 0;
      for (_i = 0, _len = keys.length; _i < _len; _i++) {
        key = keys[_i];
        if (this.data[key] != null) {
          this.stats.vsize -= this._getValLength(this._unwrap(this.data[key]));
          this.stats.ksize -= this._getKeyLength(key);
          this.stats.keys--;
          delCount++;
          delete this.data[key];
          this.emit("del", key);
        } else {
          this.stats.misses++;
        }
      }
      if (cb != null) {
        cb(null, delCount);
      }
      return delCount;
    };

    NodeCache.prototype.ttl = function() {
      var arg, args, cb, key, ttl, _i, _len;
      key = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      for (_i = 0, _len = args.length; _i < _len; _i++) {
        arg = args[_i];
        switch (typeof arg) {
          case "number":
            ttl = arg;
            break;
          case "function":
            cb = arg;
        }
      }
      ttl || (ttl = this.options.stdTTL);
      if (!key) {
        if (cb != null) {
          cb(null, false);
        }
        return false;
      }
      if ((this.data[key] != null) && this._check(key, this.data[key])) {
        if (ttl > 0) {
          this.data[key] = this._wrap(this.data[key].v, ttl);
        } else {
          this.del(key);
        }
        if (cb != null) {
          cb(null, true);
        }
        return true;
      } else {
        if (cb != null) {
          cb(null, false);
        }
        return false;
      }
    };

    NodeCache.prototype.getStats = function() {
      return this.stats;
    };

    NodeCache.prototype.flushAll = function(_startPeriod) {
      if (_startPeriod == null) {
        _startPeriod = true;
      }
      this.data = {};
      this.stats = {
        hits: 0,
        misses: 0,
        keys: 0,
        ksize: 0,
        vsize: 0
      };
      this._killCheckPeriod();
      this._checkData(_startPeriod);
      this.emit("flush");
    };

    NodeCache.prototype._checkData = function(startPeriod) {
      var key, value, _ref;
      if (startPeriod == null) {
        startPeriod = true;
      }
      _ref = this.data;
      for (key in _ref) {
        value = _ref[key];
        this._check(key, value);
      }
      if (startPeriod && this.options.checkperiod > 0) {
        this.checkTimeout = setTimeout(this._checkData, this.options.checkperiod * 1000);
      }
    };

    NodeCache.prototype._killCheckPeriod = function() {
      if (this.checkTimeout != null) {
        return clearTimeout(this.checkTimeout);
      }
    };

    NodeCache.prototype._check = function(key, data) {
      if (data.t < Date.now() && data.t !== 0) {
        process.nextTick((function(_this) {
          return function() {
            _this.del(key);
            _this.emit("expired", key, _this._unwrap(data));
          };
        })(this));
        return false;
      } else {
        return true;
      }
    };

    NodeCache.prototype._wrap = function(value, ttl) {
      var livetime, now, oReturn, ttlMultiplicator;
      now = Date.now();
      livetime = 0;
      ttlMultiplicator = 1000;
      if (ttl === 0) {
        livetime = 0;
      } else if (ttl) {
        livetime = now + (ttl * ttlMultiplicator);
      } else {
        if (this.options.stdTTL === 0) {
          livetime = this.options.stdTTL;
        } else {
          livetime = now + (this.options.stdTTL * ttlMultiplicator);
        }
      }
      return oReturn = {
        t: livetime,
        v: value
      };
    };

    NodeCache.prototype._unwrap = function(value) {
      if (value.v != null) {
        return value.v;
      }
      return null;
    };

    NodeCache.prototype._getKeyLength = function(key) {
      return key.length;
    };

    NodeCache.prototype._getValLength = function(value) {
      if (_.isString(value)) {
        return value.length;
      } else if (this.options.forceString) {
        return JSON.stringify(value).length;
      } else if (_.isArray(value)) {
        return this.options.arrayValueSize * value.length;
      } else if (_.isNumber(value)) {
        return 8;
      } else if (_.isObject(value)) {
        return this.options.objectValueSize * _.size(value);
      } else {
        return 0;
      }
    };

    NodeCache.prototype._error = function(type, data, cb) {
      var error;
      if (data == null) {
        data = {};
      }
      error = new Error();
      error.name = type;
      error.errorcode = type;
      error.msg = "-";
      error.data = data;
      if (cb && _.isFunction(cb)) {
        cb(error, null);
      } else {
        return error;
      }
    };

    return NodeCache;

  })(EventEmitter);

}).call(this);
<commit_msg>last commit broke TTL TEST. fixed that.<commit_after>// Generated by CoffeeScript 1.6.3
(function() {
  var EventEmitter, NodeCache, _,
    __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
    __hasProp = {}.hasOwnProperty,
    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    __slice = [].slice;

  _ = require("underscore");

  EventEmitter = require('events').EventEmitter;

  module.exports = NodeCache = (function(_super) {
    __extends(NodeCache, _super);

    function NodeCache(options) {
      this.options = options != null ? options : {};
      this._error = __bind(this._error, this);
      this._getValLength = __bind(this._getValLength, this);
      this._getKeyLength = __bind(this._getKeyLength, this);
      this._unwrap = __bind(this._unwrap, this);
      this._wrap = __bind(this._wrap, this);
      this._check = __bind(this._check, this);
      this._checkData = __bind(this._checkData, this);
      this.flushAll = __bind(this.flushAll, this);
      this.getStats = __bind(this.getStats, this);
      this.ttl = __bind(this.ttl, this);
      this.del = __bind(this.del, this);
      this.set = __bind(this.set, this);
      this.get = __bind(this.get, this);
      this.data = {};
      this.options = _.extend({
        forceString: false,
        objectValueSize: 80,
        arrayValueSize: 40,
        stdTTL: 0,
        checkperiod: 600
      }, this.options);
      this.stats = {
        hits: 0,
        misses: 0,
        keys: 0,
        ksize: 0,
        vsize: 0
      };
      this._checkData();
    }

    NodeCache.prototype.get = function(keys, cb) {
      var key, oRet, _i, _len;
      if (_.isString(keys)) {
        keys = [keys];
      }
      oRet = {};
      for (_i = 0, _len = keys.length; _i < _len; _i++) {
        key = keys[_i];
        if ((this.data[key] != null) && this._check(key, this.data[key])) {
          this.stats.hits++;
          oRet[key] = this._unwrap(this.data[key]);
        } else {
          this.stats.misses++;
        }
      }
      if (cb != null) {
        cb(null, oRet);
      }
      return oRet;
    };

    NodeCache.prototype.set = function(key, value, ttl, cb) {
      var existend;
      existend = false;
      if (this.options.forceString && !_.isString(value)) {
        value = JSON.stringify(value);
      }
      if (arguments.length === 3 && _.isFunction(ttl)) {
        cb = ttl;
        ttl = this.options.stdTTL;
      }
      if (this.data[key]) {
        existend = true;
        this.stats.vsize -= this._getValLength(this._unwrap(this.data[key]));
      }
      this.data[key] = this._wrap(value, ttl);
      this.stats.vsize += this._getValLength(value);
      if (!existend) {
        this.stats.ksize += this._getKeyLength(key);
        this.stats.keys++;
      }
      this.emit("set", key, value);
      if (cb != null) {
        cb(null, true);
      }
      return true;
    };

    NodeCache.prototype.del = function(keys, cb) {
      var delCount, key, _i, _len;
      if (_.isString(keys)) {
        keys = [keys];
      }
      delCount = 0;
      for (_i = 0, _len = keys.length; _i < _len; _i++) {
        key = keys[_i];
        if (this.data[key] != null) {
          this.stats.vsize -= this._getValLength(this._unwrap(this.data[key]));
          this.stats.ksize -= this._getKeyLength(key);
          this.stats.keys--;
          delCount++;
          delete this.data[key];
          this.emit("del", key);
        } else {
          this.stats.misses++;
        }
      }
      if (cb != null) {
        cb(null, delCount);
      }
      return delCount;
    };

    NodeCache.prototype.ttl = function() {
      var arg, args, cb, key, ttl, _i, _len;
      key = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
      for (_i = 0, _len = args.length; _i < _len; _i++) {
        arg = args[_i];
        switch (typeof arg) {
          case "number":
            ttl = arg;
            break;
          case "function":
            cb = arg;
        }
      }
      ttl || (ttl = this.options.stdTTL);
      if (!key) {
        if (cb != null) {
          cb(null, false);
        }
        return false;
      }
      if ((this.data[key] != null) && this._check(key, this.data[key])) {
        if (ttl > 0) {
          this.data[key] = this._wrap(this.data[key].v, ttl);
        } else {
          this.del(key);
        }
        if (cb != null) {
          cb(null, true);
        }
        return true;
      } else {
        if (cb != null) {
          cb(null, false);
        }
        return false;
      }
    };

    NodeCache.prototype.getStats = function() {
      return this.stats;
    };

    NodeCache.prototype.flushAll = function(_startPeriod) {
      if (_startPeriod == null) {
        _startPeriod = true;
      }
      this.data = {};
      this.stats = {
        hits: 0,
        misses: 0,
        keys: 0,
        ksize: 0,
        vsize: 0
      };
      this._killCheckPeriod();
      this._checkData(_startPeriod);
      this.emit("flush");
    };

    NodeCache.prototype._checkData = function(startPeriod) {
      var key, value, _ref;
      if (startPeriod == null) {
        startPeriod = true;
      }
      _ref = this.data;
      for (key in _ref) {
        value = _ref[key];
        this._check(key, value);
      }
      if (startPeriod && this.options.checkperiod > 0) {
        this.checkTimeout = setTimeout(this._checkData, this.options.checkperiod * 1000);
      }
    };

    NodeCache.prototype._killCheckPeriod = function() {
      if (this.checkTimeout != null) {
        return clearTimeout(this.checkTimeout);
      }
    };

    NodeCache.prototype._check = function(key, data) {
      if (data.t < Date.now() && data.t !== 0) {
        this.del(key);
        this.emit("expired", key, this._unwrap(data));
        return false;
      } else {
        return true;
      }
    };

    NodeCache.prototype._wrap = function(value, ttl) {
      var livetime, now, oReturn, ttlMultiplicator;
      now = Date.now();
      livetime = 0;
      ttlMultiplicator = 1000;
      if (ttl === 0) {
        livetime = 0;
      } else if (ttl) {
        livetime = now + (ttl * ttlMultiplicator);
      } else {
        if (this.options.stdTTL === 0) {
          livetime = this.options.stdTTL;
        } else {
          livetime = now + (this.options.stdTTL * ttlMultiplicator);
        }
      }
      return oReturn = {
        t: livetime,
        v: value
      };
    };

    NodeCache.prototype._unwrap = function(value) {
      if (value.v != null) {
        return value.v;
      }
      return null;
    };

    NodeCache.prototype._getKeyLength = function(key) {
      return key.length;
    };

    NodeCache.prototype._getValLength = function(value) {
      if (_.isString(value)) {
        return value.length;
      } else if (this.options.forceString) {
        return JSON.stringify(value).length;
      } else if (_.isArray(value)) {
        return this.options.arrayValueSize * value.length;
      } else if (_.isNumber(value)) {
        return 8;
      } else if (_.isObject(value)) {
        return this.options.objectValueSize * _.size(value);
      } else {
        return 0;
      }
    };

    NodeCache.prototype._error = function(type, data, cb) {
      var error;
      if (data == null) {
        data = {};
      }
      error = new Error();
      error.name = type;
      error.errorcode = type;
      error.msg = "-";
      error.data = data;
      if (cb && _.isFunction(cb)) {
        cb(error, null);
      } else {
        return error;
      }
    };

    return NodeCache;

  })(EventEmitter);

}).call(this);
<|endoftext|>