<commit_before>window.AutoCompletedAddToChannelView = Backbone.View.extend({
  tagName: "div",
  className: "add_to_channel",

  events: {
    "keydown input.typeahead": "parseKeyDown",
    "keyup input.typeahead": "autoComplete",
    "focus input.typeahead": "focusInput",
    "click div.auto_complete": "parseReturn",
    "click div.fake_input a": "parseReturn",
    "blur input.typeahead": "blurInput",
    "click .show-input-button": "showInput",
    "mouseenter .auto_complete>div": "selectAddNew",
    "mouseleave .auto_complete>div": "deActivateAddNew"
  },

  tmpl: HoganTemplates["channels/_auto_completed_add_to_channel"],

  initialize: function () {
    this.collection = new OwnChannelCollection();
    this.collection.bind('add', this.addChannel, this);

    this._channelViews = {};
  },

  render: function () {
    this.$el.html( this.tmpl.render() );

    this.reset();

    return this;
  },

  parseKeyDown: function (e) {
    this._proceed = false;

    switch(e.keyCode) {
      case 13:
        this.parseReturn(e);
        break;
      case 40:
        this.moveSelectionDown(e);
        break;
      case 38:
        this.moveSelectionUp(e);
        break;
      case 27:
        this.hideAutoComplete();
        break;
      default:
        this._proceed = true;
        break;
    }
  },

  showInput: function () {
    this.$el
      .removeClass('hide-input')
      .find('.fake_input input').focus();
  },

  addChannel: function (channel) {
    var view = new AutoCompletedAddedChannelView({
      model: channel,
      rootView: this
    }).render();

    this.$el.addClass('hide-input');

    this.$el.find('.added_channels').append( view.el );

    this._channelViews[channel.id] = view;

    if ( window.updateHeight ) {
      window.updateHeight();
    }
  },

  reset: function () {
    _.each(this._channelViews, function (view) {
      view.remove();
    });

    this._channelViews = {};

    this.collection.each(function(channel) {
      this.addChannel(channel);
    }, this);
  },

  focusInput: function () { this.$el.addClass('focus'); },
  blurInput: function () { this.$el.removeClass('focus'); },

  moveSelectionUp: function (e) {
    var prevKey;

    if ( this._activeChannelKey !== undefined ) {
      // There is an active channel key
      if ( this._autoCompleteViews[this._activeChannelKey - 1] ) {
        // Select the previous key
        prevKey = this._activeChannelKey - 1;
      }
    }

    if ( typeof prevKey === "undefined" ) {
      // No previous item to be selected
      if ( ! this.isAddNewActive() ) {
        // Select addNew
        this.selectAddNew();

        if ( ! this.isAddNewActive() ) {
          prevKey = this._autoCompleteViews.length - 1;
        }
      } else {
        prevKey = this._autoCompleteViews.length - 1;
      }
    }

    if ( typeof prevKey === "number" ) {
      this.setActiveAutoComplete(prevKey, true);
    }

    e.preventDefault();
  },

  moveSelectionDown: function (e) {
    var nextKey;

    if ( this._activeChannelKey !== undefined ) {
      if ( this._autoCompleteViews[this._activeChannelKey + 1] ) {
        nextKey = this._activeChannelKey + 1;
      }
    } else {
      nextKey = 0;
    }

    if ( typeof nextKey === "undefined" ) {
      this.selectAddNew();

      if ( ! this.isAddNewActive() ) {
        nextKey = 0;
      }
    }

    if ( typeof nextKey === "number" ) {
      this.setActiveAutoComplete(nextKey, false);
    }

    e.preventDefault();
  },

  setActiveAutoComplete: function (key, scroll) {
    if ( this._autoCompleteViews[this._activeChannelKey] ) {
      this._autoCompleteViews[this._activeChannelKey].deActivate();
    }

    this.deActivateAddNew();

    this._autoCompleteViews[key].activate();

    if ( typeof scroll === "boolean" ) {
      this._autoCompleteViews[key].el.scrollIntoView(scroll);
    }

    this._activeChannelKey = key;
  },

  selectAddNew: function () {
    if ( ! this.isAddNewVisible() ) {
      return false;
    }

    if ( typeof this._activeChannelKey === "number" ) {
      this._autoCompleteViews[this._activeChannelKey].deActivate();

      this._activeChannelKey = undefined;
    }

    this.activateAddNew();
  },

  activateAddNew: function () {
    this.$el.find('.auto_complete>div').addClass('active');
  },

  deActivateAddNew: function () {
    this.$el.find('.auto_complete>div').removeClass('active');
  },

  isAddNewActive: function () {
    return this.$el.find('.auto_complete>div').hasClass('active');
  },

  activateAutoCompleteView: function (view) {
    _.each(this._autoCompleteViews, function (autocomplete, key) {
      if ( view === autocomplete ) {
        this.setActiveAutoComplete(key);
      }
    }, this);
  },

  deActivateAutoCompleteView: function (view) {
    this._autoCompleteViews[ this._activeChannelKey ].deActivate();

    this._activeChannelKey = undefined;
  },

  parseReturn: function (e) {
    e.preventDefault();
    e.stopPropagation();

    this.disable();

    if ( this._activeChannelKey !== undefined ) {
      var selected = this._autoCompleteViews[ this._activeChannelKey].model;

      this.$el.find('input.typeahead').val( selected.get('title') );

      if ( selected.get('user_channel') ) {
        this.addNewChannel( selected.get('user_channel') );

        return;
      }
    }

    this.createNewChannel(e);
  },

  createNewChannel: function (e) {
    var title = this.$el.find('input.typeahead').val();

    this.showLoading();

    var dupe = false;

    this.collection.each(function (channel) {
      if ( channel.get('title') === title ) {
        dupe = true;
      }
    });

    if ( dupe ) {
      this.hideAutoComplete();

      this.enable();
      this.hideLoading();
      this.clearInput();

      return;
    }

    $.ajax({
      url: '/' + currentUser.get('username') + '/channels',
      data: {
        title: title
      },
      type: "POST"
    }).done( _.bind( this.addNewChannel, this ) );

    e.preventDefault();
  },

  addNewChannel: function (channel) {
    channel = new Channel(channel);

    currentUser.channels.add(channel);
    this.collection.add(channel);

    this.hideAutoComplete();

    this.enable();
    this.hideLoading();
    this.clearInput();
  },

  clearInput: function () {
    this.$el.find('input.typeahead').val('');

    if ( this.collection.length ) {
      this.$el.addClass('hide-input');
    }
  },

  disable: function () {
    this.$el
      .addClass('disabled')
      .find('input.typeahead').prop('disabled', true);

    this.$el.find('.btn').addClass('disabled');
  },

  enable: function () {
    this.$el
      .removeClass('disabled')
      .find('input.typeahead').prop('disabled', false);

    this.$el.find('.btn').removeClass('disabled');
  },

  showLoading: function () {
    this.$el.find('.loading').show();
  },

  hideLoading: function () {
    this.$el.find('.loading').hide();
  },

  updateText: function () {
    var value = this.$el.find('input.typeahead').val();
    if ( value.length ) {
      this.$el.addClass('has-text');

      this.$el.find(".search").text( value );
    } else {
      this.$el.removeClass('has-text');
    }
  },

  autoComplete: _.throttle(function () {
    var searchValue = this.$el.find('input.typeahead').val();

    this.updateText();

    if ( this._lastKnownSearchValue === searchValue
        || ! this._proceed ) {
      return;
    }

    this._lastKnownSearchValue = searchValue;
    this._activeChannelKey = undefined;

    if (searchValue.length < 1) {
      this.hideAutoComplete();
      return;
    }

    this.showLoading();

    $.ajax({
      type: "GET",
      url: "/" + currentUser.get('username') + "/channels/find",
      data: {
        s: searchValue
      }
    }).done( _.bind( function (data) {
      var channels = _.map(data, function (channel) {
        return new AutoCompletedChannel(channel);
      });

      this.updateAutoComplete(channels);

      this.hideLoading();
    }, this ) );
  }, 300),

  updateAutoComplete: function (channels) {
    this.clearAutoComplete();

    _.each(channels, this.addAutoComplete, this);

    this.showAutoComplete();

    if ( window.updateHeight ) {
      window.updateHeight();
    }
  },

  hideAddNew: function () {
    this.$el.addClass('hide-add-new');
  },

  showAddNew: function () {
    this.$el.removeClass('hide-add-new');
  },

  isAddNewVisible: function () {
    return ! this.$el.hasClass('hide-add-new');
  },

  hideAutoComplete: function () {
    this.$el.find('.auto_complete').hide();
  },

  showAutoComplete: function () {
    this.$el.find('.auto_complete').show();
  },

  clearAutoComplete: function () {
    _.each( this._autoCompleteViews, function (view) {
      view.remove();
    });

    this._autoCompleteViews = [];
    this._autoCompletes = [];

    this.$el.find('.auto_complete').addClass('empty');

    this.hideAutoComplete();

    this.showAddNew();
  },

  addAutoComplete: function (channel) {
    if ( ! ( channel.get('user_channel')
                && this.collection.get( channel.get('user_channel').id ) ) ) {
      this._autoCompletes.push(channel);

      var view = new AutoCompletedChannelView({
        model: channel,
        query: this._lastKnownSearchValue,
        parent: this
      }).render();

      this.$el.find('.auto_complete>ul').append(view.el);

      this.$el.find('.auto_complete').removeClass('empty');

      this._autoCompleteViews.push(view);

      this.showAutoComplete();

      if ( channel.get('user_channel') ) {
        var lowerCaseTitle = channel.get('user_channel').title.toLowerCase();
        var lowerCaseSearch = this._lastKnownSearchValue.toLowerCase();

        if ( lowerCaseSearch === lowerCaseTitle ) {
          this.hideAddNew();
        }
      }
    }
  },

  removeAddedChannel: function (id) {
    this._channelViews[id].remove();

    delete this._channelViews[id];

    this.collection.remove(id);

    if ( this.collection.length ) {
      this.$el.addClass("hide-input");
    } else {
      this.$el.removeClass("hide-input");
    }
  }
});
<commit_msg>De-activate the "Add new" button when resetting the autocomplete<commit_after>window.AutoCompletedAddToChannelView = Backbone.View.extend({
  tagName: "div",
  className: "add_to_channel",

  events: {
    "keydown input.typeahead": "parseKeyDown",
    "keyup input.typeahead": "autoComplete",
    "focus input.typeahead": "focusInput",
    "click div.auto_complete": "parseReturn",
    "click div.fake_input a": "parseReturn",
    "blur input.typeahead": "blurInput",
    "click .show-input-button": "showInput",
    "mouseenter .auto_complete>div": "selectAddNew",
    "mouseleave .auto_complete>div": "deActivateAddNew"
  },

  tmpl: HoganTemplates["channels/_auto_completed_add_to_channel"],

  initialize: function () {
    this.collection = new OwnChannelCollection();
    this.collection.bind('add', this.addChannel, this);

    this._channelViews = {};
  },

  render: function () {
    this.$el.html( this.tmpl.render() );

    this.reset();

    return this;
  },

  parseKeyDown: function (e) {
    this._proceed = false;

    switch(e.keyCode) {
      case 13:
        this.parseReturn(e);
        break;
      case 40:
        this.moveSelectionDown(e);
        break;
      case 38:
        this.moveSelectionUp(e);
        break;
      case 27:
        this.hideAutoComplete();
        break;
      default:
        this._proceed = true;
        break;
    }
  },

  showInput: function () {
    this.$el
      .removeClass('hide-input')
      .find('.fake_input input').focus();
  },

  addChannel: function (channel) {
    var view = new AutoCompletedAddedChannelView({
      model: channel,
      rootView: this
    }).render();

    this.$el.addClass('hide-input');

    this.$el.find('.added_channels').append( view.el );

    this._channelViews[channel.id] = view;

    if ( window.updateHeight ) {
      window.updateHeight();
    }
  },

  reset: function () {
    _.each(this._channelViews, function (view) {
      view.remove();
    });

    this._channelViews = {};

    this.collection.each(function(channel) {
      this.addChannel(channel);
    }, this);
  },

  focusInput: function () { this.$el.addClass('focus'); },
  blurInput: function () { this.$el.removeClass('focus'); },

  moveSelectionUp: function (e) {
    var prevKey;

    if ( this._activeChannelKey !== undefined ) {
      // There is an active channel key
      if ( this._autoCompleteViews[this._activeChannelKey - 1] ) {
        // Select the previous key
        prevKey = this._activeChannelKey - 1;
      }
    }

    if ( typeof prevKey === "undefined" ) {
      // No previous item to be selected
      if ( ! this.isAddNewActive() ) {
        // Select addNew
        this.selectAddNew();

        if ( ! this.isAddNewActive() ) {
          prevKey = this._autoCompleteViews.length - 1;
        }
      } else {
        prevKey = this._autoCompleteViews.length - 1;
      }
    }

    if ( typeof prevKey === "number" ) {
      this.setActiveAutoComplete(prevKey, true);
    }

    e.preventDefault();
  },

  moveSelectionDown: function (e) {
    var nextKey;

    if ( this._activeChannelKey !== undefined ) {
      if ( this._autoCompleteViews[this._activeChannelKey + 1] ) {
        nextKey = this._activeChannelKey + 1;
      }
    } else {
      nextKey = 0;
    }

    if ( typeof nextKey === "undefined" ) {
      this.selectAddNew();

      if ( ! this.isAddNewActive() ) {
        nextKey = 0;
      }
    }

    if ( typeof nextKey === "number" ) {
      this.setActiveAutoComplete(nextKey, false);
    }

    e.preventDefault();
  },

  setActiveAutoComplete: function (key, scroll) {
    if ( this._autoCompleteViews[this._activeChannelKey] ) {
      this._autoCompleteViews[this._activeChannelKey].deActivate();
    }

    this.deActivateAddNew();

    this._autoCompleteViews[key].activate();

    if ( typeof scroll === "boolean" ) {
      this._autoCompleteViews[key].el.scrollIntoView(scroll);
    }

    this._activeChannelKey = key;
  },

  selectAddNew: function () {
    if ( ! this.isAddNewVisible() ) {
      return false;
    }

    if ( typeof this._activeChannelKey === "number" ) {
      this._autoCompleteViews[this._activeChannelKey].deActivate();

      this._activeChannelKey = undefined;
    }

    this.activateAddNew();
  },

  activateAddNew: function () {
    this.$el.find('.auto_complete>div').addClass('active');
  },

  deActivateAddNew: function () {
    this.$el.find('.auto_complete>div').removeClass('active');
  },

  isAddNewActive: function () {
    return this.$el.find('.auto_complete>div').hasClass('active');
  },

  activateAutoCompleteView: function (view) {
    _.each(this._autoCompleteViews, function (autocomplete, key) {
      if ( view === autocomplete ) {
        this.setActiveAutoComplete(key);
      }
    }, this);
  },

  deActivateAutoCompleteView: function (view) {
    this._autoCompleteViews[ this._activeChannelKey ].deActivate();

    this._activeChannelKey = undefined;
  },

  parseReturn: function (e) {
    e.preventDefault();
    e.stopPropagation();

    this.disable();

    if ( this._activeChannelKey !== undefined ) {
      var selected = this._autoCompleteViews[ this._activeChannelKey].model;

      this.$el.find('input.typeahead').val( selected.get('title') );

      if ( selected.get('user_channel') ) {
        this.addNewChannel( selected.get('user_channel') );

        return;
      }
    }

    this.createNewChannel(e);
  },

  createNewChannel: function (e) {
    var title = this.$el.find('input.typeahead').val();

    this.showLoading();

    var dupe = false;

    this.collection.each(function (channel) {
      if ( channel.get('title') === title ) {
        dupe = true;
      }
    });

    if ( dupe ) {
      this.hideAutoComplete();

      this.enable();
      this.hideLoading();
      this.clearInput();

      return;
    }

    $.ajax({
      url: '/' + currentUser.get('username') + '/channels',
      data: {
        title: title
      },
      type: "POST"
    }).done( _.bind( this.addNewChannel, this ) );

    e.preventDefault();
  },

  addNewChannel: function (channel) {
    channel = new Channel(channel);

    currentUser.channels.add(channel);
    this.collection.add(channel);

    this.hideAutoComplete();

    this.enable();
    this.hideLoading();
    this.clearInput();
  },

  clearInput: function () {
    this.$el.find('input.typeahead').val('');

    if ( this.collection.length ) {
      this.$el.addClass('hide-input');
    }
  },

  disable: function () {
    this.$el
      .addClass('disabled')
      .find('input.typeahead').prop('disabled', true);

    this.$el.find('.btn').addClass('disabled');
  },

  enable: function () {
    this.$el
      .removeClass('disabled')
      .find('input.typeahead').prop('disabled', false);

    this.$el.find('.btn').removeClass('disabled');
  },

  showLoading: function () {
    this.$el.find('.loading').show();
  },

  hideLoading: function () {
    this.$el.find('.loading').hide();
  },

  updateText: function () {
    var value = this.$el.find('input.typeahead').val();
    if ( value.length ) {
      this.$el.addClass('has-text');

      this.$el.find(".search").text( value );
    } else {
      this.$el.removeClass('has-text');
    }
  },

  autoComplete: _.throttle(function () {
    var searchValue = this.$el.find('input.typeahead').val();

    this.updateText();

    if ( this._lastKnownSearchValue === searchValue
        || ! this._proceed ) {
      return;
    }

    this._lastKnownSearchValue = searchValue;
    this._activeChannelKey = undefined;

    if (searchValue.length < 1) {
      this.hideAutoComplete();
      return;
    }

    this.showLoading();

    $.ajax({
      type: "GET",
      url: "/" + currentUser.get('username') + "/channels/find",
      data: {
        s: searchValue
      }
    }).done( _.bind( function (data) {
      var channels = _.map(data, function (channel) {
        return new AutoCompletedChannel(channel);
      });

      this.updateAutoComplete(channels);

      this.hideLoading();
    }, this ) );
  }, 300),

  updateAutoComplete: function (channels) {
    this.clearAutoComplete();

    _.each(channels, this.addAutoComplete, this);

    this.showAutoComplete();

    if ( window.updateHeight ) {
      window.updateHeight();
    }
  },

  hideAddNew: function () {
    this.$el.addClass('hide-add-new');
  },

  showAddNew: function () {
    this.$el.removeClass('hide-add-new');
  },

  isAddNewVisible: function () {
    return ! this.$el.hasClass('hide-add-new');
  },

  hideAutoComplete: function () {
    this.$el.find('.auto_complete').hide();
  },

  showAutoComplete: function () {
    this.$el.find('.auto_complete').show();
  },

  clearAutoComplete: function () {
    _.each( this._autoCompleteViews, function (view) {
      view.remove();
    });

    this._autoCompleteViews = [];
    this._autoCompletes = [];

    this.$el.find('.auto_complete').addClass('empty');

    this.hideAutoComplete();

    this.deActivateAddNew();

    this.showAddNew();
  },

  addAutoComplete: function (channel) {
    if ( ! ( channel.get('user_channel')
                && this.collection.get( channel.get('user_channel').id ) ) ) {
      this._autoCompletes.push(channel);

      var view = new AutoCompletedChannelView({
        model: channel,
        query: this._lastKnownSearchValue,
        parent: this
      }).render();

      this.$el.find('.auto_complete>ul').append(view.el);

      this.$el.find('.auto_complete').removeClass('empty');

      this._autoCompleteViews.push(view);

      this.showAutoComplete();

      if ( channel.get('user_channel') ) {
        var lowerCaseTitle = channel.get('user_channel').title.toLowerCase();
        var lowerCaseSearch = this._lastKnownSearchValue.toLowerCase();

        if ( lowerCaseSearch === lowerCaseTitle ) {
          this.hideAddNew();
        }
      }
    }
  },

  removeAddedChannel: function (id) {
    this._channelViews[id].remove();

    delete this._channelViews[id];

    this.collection.remove(id);

    if ( this.collection.length ) {
      this.$el.addClass("hide-input");
    } else {
      this.$el.removeClass("hide-input");
    }
  }
});
<|endoftext|>


<commit_before>
import QueryParser from '../utils/query-parser';

export default function(req, res, next) {
    req.parsedQuery = new QueryParser(req.db, req.resource.name, req.query);
    return next();
}
<commit_msg>send bad request if query parser returns errors<commit_after>
import QueryParser from '../utils/query-parser';

export default function(req, res, next) {
    req.parsedQuery = new QueryParser(req.db, req.resource.name, req.query);

    var errors = req.parsedQuery.errors;
    if (errors) {
        return res.badRequest('bad request', {'issue': 'bad query', reasons: errors});
    }

    return next();
}
<|endoftext|>


<commit_before>"use strict";

import url from 'url';
import path from 'path';
import gm from 'gm';
import Promise from 'promise';
import {log,error} from './utils';

function headers(contentType) {
  let extraHeaders = arguments.length > 1 ? arguments[1] : {};
  return Object.assign({
    'Date': dateHeaderValue(new Date()),
    'Content-Type': contentType
  }, extraHeaders);
}

function dateHeaderValue(dt) {
  return dt.toUTCString();
}

function handleFailedImageResponse(imageResponse, res) {
  let h = headers(imageResponse.headers['content-type'], {'Cache-Control': 'no-cache'});
  res.writeHead(imageResponse.statusCode, h);
  imageResponse.on('error', function(err) {
    error('handleFailedImageResponse imageResponse stream error: %o', err);
    try { imageResponse.close(); } catch(e) {}
    res.end();
  });
  imageResponse.pipe(res);
}

function failResponse(res, message) {
  res.writeHead(500, headers('text/plain', {'Cache-Control': 'no-cache'}));
  res.end(message);
}

class Processor {
  constructor(route, imageSource, imageQueue, options, processFunc) {
    this.route = route;
    this.imageSource = imageSource;
    this.imageQueue = imageQueue;
    this.processFunc = processFunc;

    if ('cacheExpiration' in options) {
      this.cacheExpiration = options.cacheExpiration;
    } else {
      this.cacheExpiration = null;
    }
    if ('socketTimeout' in options) {
      this.socketTimeout = options.socketTimeout;
    } else {
      this.socketTimeout = null;
    }
    if ('gifFirstFrame' in options) {
      this.gifFirstFrame = options.gifFirstFrame;
    } else {
      this.gifFirstFrame = false;
    }
  }

  process(context, res) {
    context.params = this.route.extractParameters(context.request);
    log('params: %o', context.params);
    return this._getImage(context, res).
      then( (image) => this.processImage(image, context, res) ).
      then( (image) => this._streamImage(image, context, res) ).
      then( () => this._cleanup(context) );
  }

  processImage(image, context, res) {
    return this._executeProcessFunction(image, context).
      catch(function(err) {
        error("error in process function: %o", err);
        failResponse(res, err.message);
        return Promise.reject(err);
      });
  }

  _getImage(context, res) {
    let gifFirstFrame = this.gifFirstFrame;
    return this._requestImage(context).
      then(function(imageResponse) {
        if (imageResponse.statusCode != 200) {
          handleFailedImageResponse(imageResponse, res);
          return Promise.reject(new Error("Unexpected HTTP response code: " + imageResponse.statusCode));
        } else {
          context.imageContentType = imageResponse.headers['content-type'];
          context.imageLastModified = imageResponse.headers['last-modified'];
          log("received image: %s", context.imageContentType);
          let filename = path.basename(context.imageUrl);
          if (gifFirstFrame && filename.match(/\.gif$/)) {
            log('processing first frame of gif: %s', filename);
            filename = filename + '[0]';
          }
          return gm(imageResponse, filename);
        }
      }, function(err) {
        error("error fetching image: %o", err);
        failResponse(res, err.message);
        return Promise.reject(err);
      });
  }

  _requestImage(context) {
    return this.imageQueue.push(
      this.imageSource,
      context,
      { socketTimeout: this.socketTimeout }
    );
  }

  _executeProcessFunction(image, context) {
    let promise = null;
    try {
      log('executing process image function...');
      let result = this.processFunc(image, context);
      if (result == null || typeof result == 'undefined') {
        promise = Promise.reject(new Error('Expecting promise or image as result of process function'));
      } else if (('constructor' in result) && result.constructor == Promise) {
        promise = result;
      } else {
        promise = Promise.resolve(result);
      }
    } catch(err) {
      promise = Promise.reject(err);
    }
    return promise;
  }

  _streamImage(image, context, res) {
    log('streaming processed image...');
    let proc = this;
    return this._createImageStream(image, context).
      then(function(stream) {
        return proc._pipeStreamToResponse(stream, context, res);
      }, function(err) {
        error("error processing image: %o", err);
        failResponse(res, err.message);
        return Promise.reject(err);
      });
  }

  _createImageStream(image, context) {
    return new Promise(function(resolve, reject) {
      image.stream(function(err, stdout, stderr) {
        if (err) {
          reject(err);
        } else {
          resolve(stdout);
        }
      });
    });
  }

  _pipeStreamToResponse(stream, context, res) {
    log('sending transformed image...');
    let cacheExpiration = this.cacheExpiration;
    let h = headers(context.imageContentType, {'Last-Modified': context.imageLastModified});
    if (cacheExpiration) {
      h['Expires'] = dateHeaderValue(new Date(new Date().getTime() + (cacheExpiration*1000)));
      h['Cache-Control'] = "public, max-age=" + cacheExpiration;
    }
    res.writeHead(200, h);
    return this._pipeStream(stream, res).
      catch(function(err) {
        error('error streaming image to response: %o', err);
        failResponse(res, err.message);
        res.end();
        return Promise.reject(err);
      });
  }

  _pipeStream(a,b) {
    return new Promise(function(resolve, reject) {
      let streamError = false;
      a.on('finish', function() {
          if (!streamError) { resolve(); }
        }).
        on('error', function(err) {
          streamError = true;
          reject(err);
          try { a.close(); } catch(e) {}
        });
      a.pipe(b);
    });
  }

  _cleanup(context) {
    if (context._cleanupFunctions) {
      for (let i = 0, len = context._cleanupFunctions.length; i < len; i++) {
        let func = context._cleanupFunctions[0];
        log('performing cleanup: %o', func);
        try {
          func();
          log('cleanup completed');
        } catch (err) {
          error('error in cleanup function: %o', err);
        }
      }
    }
  }

}

module.exports = Processor;
<commit_msg>Write Content-Length header<commit_after>"use strict";

import url from 'url';
import path from 'path';
import gm from 'gm';
import Promise from 'promise';
import {log,error} from './utils';

function headers(contentType) {
  let extraHeaders = arguments.length > 1 ? arguments[1] : {};
  return Object.assign({
    'Date': dateHeaderValue(new Date()),
    'Content-Type': contentType
  }, extraHeaders);
}

function dateHeaderValue(dt) {
  return dt.toUTCString();
}

function handleFailedImageResponse(imageResponse, res) {
  let h = headers(imageResponse.headers['content-type'], {'Cache-Control': 'no-cache'});
  res.writeHead(imageResponse.statusCode, h);
  imageResponse.on('error', function(err) {
    error('handleFailedImageResponse imageResponse stream error: %o', err);
    try { imageResponse.close(); } catch(e) {}
    res.end();
  });
  imageResponse.pipe(res);
}

function failResponse(res, message) {
  res.writeHead(500, headers('text/plain', {'Cache-Control': 'no-cache'}));
  res.end(message);
}

class Processor {
  constructor(route, imageSource, imageQueue, options, processFunc) {
    this.route = route;
    this.imageSource = imageSource;
    this.imageQueue = imageQueue;
    this.processFunc = processFunc;

    if ('cacheExpiration' in options) {
      this.cacheExpiration = options.cacheExpiration;
    } else {
      this.cacheExpiration = null;
    }
    if ('socketTimeout' in options) {
      this.socketTimeout = options.socketTimeout;
    } else {
      this.socketTimeout = null;
    }
    if ('gifFirstFrame' in options) {
      this.gifFirstFrame = options.gifFirstFrame;
    } else {
      this.gifFirstFrame = false;
    }
  }

  process(context, res) {
    context.params = this.route.extractParameters(context.request);
    log('params: %o', context.params);
    return this._getImage(context, res).
      then( (image) => this._processImage(image, context, res) ).
      then( (image) => this._writeImage(image, context, res) ).
      then( () => this._cleanup(context) );
  }

  _processImage(image, context, res) {
    return this._executeProcessFunction(image, context).
      catch(function(err) {
        error("error in process function: %o", err);
        failResponse(res, err.message);
        return Promise.reject(err);
      });
  }

  _getImage(context, res) {
    let gifFirstFrame = this.gifFirstFrame;
    return this.imageQueue.push(this.imageSource, context, {socketTimeout: this.socketTimeout}).
      then(function(imageResponse) {
        if (imageResponse.statusCode != 200) {
          handleFailedImageResponse(imageResponse, res);
          return Promise.reject(new Error("Unexpected HTTP response code: " + imageResponse.statusCode));
        } else {
          context.imageContentType = imageResponse.headers['content-type'];
          context.imageLastModified = imageResponse.headers['last-modified'];
          log("received image: %s", context.imageContentType);
          let filename = path.basename(context.imageUrl);
          if (gifFirstFrame && filename.match(/\.gif$/)) {
            log('processing first frame of gif: %s', filename);
            filename = filename + '[0]';
          }
          return gm(imageResponse, filename);
        }
      }, function(err) {
        error("error fetching image: %o", err);
        failResponse(res, err.message);
        return Promise.reject(err);
      });
  }

  _executeProcessFunction(image, context) {
    let promise = null;
    try {
      log('executing process image function...');
      let result = this.processFunc(image, context);
      if (result == null || typeof result == 'undefined') {
        promise = Promise.reject(new Error('Expecting promise or image as result of process function'));
      } else if (('constructor' in result) && result.constructor == Promise) {
        promise = result;
      } else {
        promise = Promise.resolve(result);
      }
    } catch(err) {
      promise = Promise.reject(err);
    }
    return promise;
  }

  _bufferImage(image) {
    return new Promise(function(resolve, reject) {
      log("buffering final image...");
      image.toBuffer(function(err, buf) {
        if (err) {
          reject(err);
        } else {
          resolve(buf);
        }
      });
    });
  }

  _writeImage(image, context, res) {
    let proc = this;
    return this._bufferImage(image).
      then(function(buffer) {
        log("writing image...");
        context.imageFileSize = buffer.length;
        proc._writeResponseHead(context, res);
        res.write(buffer);
        res.end();
      }).
      catch(function(err) {
        error("error writing image: %o", err);
        failResponse(res, err.message);
        return Promise.reject(err);
      });
  }

  _streamImage(image, context, res) {
    log('streaming processed image...');
    let proc = this;
    return this._createImageStream(image, context).
      then(function(stream) {
        return proc._pipeStreamToResponse(stream, context, res);
      }, function(err) {
        error("error processing image: %o", err);
        failResponse(res, err.message);
        return Promise.reject(err);
      });
  }

  _createImageStream(image, context) {
    return new Promise(function(resolve, reject) {
      image.stream(function(err, stdout, stderr) {
        if (err) {
          reject(err);
        } else {
          resolve(stdout);
        }
      });
    });
  }

  _writeResponseHead(context, res) {
    let cacheExpiration = this.cacheExpiration;
    let h = headers(context.imageContentType, {'Last-Modified': context.imageLastModified});
    if (cacheExpiration) {
      h['Expires'] = dateHeaderValue(new Date(new Date().getTime() + (cacheExpiration*1000)));
      h['Cache-Control'] = "public, max-age=" + cacheExpiration;
    }
    if (context.imageFileSize) {
      h['Content-Length'] = context.imageFileSize;
    }
    res.writeHead(200, h);
  }

  _pipeStreamToResponse(stream, context, res) {
    log('sending transformed image...');
    this._writeResponseHead(context, res);
    return this._pipeStream(stream, res).
      catch(function(err) {
        error('error streaming image to response: %o', err);
        failResponse(res, err.message);
        res.end();
        return Promise.reject(err);
      });
  }

  _pipeStream(a,b) {
    return new Promise(function(resolve, reject) {
      let streamError = false;
      a.on('finish', function() {
          if (!streamError) { resolve(); }
        }).
        on('error', function(err) {
          streamError = true;
          reject(err);
          try { a.close(); } catch(e) {}
        });
      a.pipe(b);
    });
  }

  _cleanup(context) {
    if (context._cleanupFunctions) {
      for (let i = 0, len = context._cleanupFunctions.length; i < len; i++) {
        let func = context._cleanupFunctions[0];
        log('performing cleanup: %o', func);
        try {
          func();
          log('cleanup completed');
        } catch (err) {
          error('error in cleanup function: %o', err);
        }
      }
    }
  }

}

module.exports = Processor;
<|endoftext|>


<commit_before>const fs = require('fs');
const async = require('async');
const queryFields = ['excerpt', 'name', 'title'];

function createResponseResults(docs) {
    return {
        docs,
        limit: docs.length,
        total: docs.length,
        start: 0,
        end: docs.length,
        page: 1,
        pages: 1
    };
}

// It instantiates a local database asynchronously.
module.exports = flatDb = (filename, categoryFile, callback) => {
    var dbStore;
    async.series([
        (cb) => {
            fs.readFile(filename, {encoding: 'utf8'}, (err, data) => {
                if (err)
                    return cb(err, null);

                // Parse data
                const rawData = JSON.parse(data);
                dbStore = Object.keys(rawData).map(key => rawData[key]);

                // Adds various library-specific data handles
                dbStore._filename = filename;

                dbStore.latest = () => createResponseResults(
                    dbStore.sort((a, b) => a.buildDate.localeCompare(b.buildDate)).slice(0, 10));

                dbStore.search = (query, options, caback) => {
                    options = options || {};
                    var
                        limit = options.limit || 20,
                        page = options.page || 1,
                        start = (page - 1) * limit,
                        end = limit * (page),
                        pages, total;

                    var result = dbStore;

                    if (query ) {
                        result = result.filter((item) => {
                            return queryFields.map(field => item[field].includes(query)).indexOf(true) > -1;
                        });

                    }
                    total = result.length;
                    pages = Math.floor(total / limit) || 1;
                    caback(null, {
                        limit,
                        start,
                        end,
                        total,
                        page,
                        pages,
                        docs: result.slice(start, end),
                    });
                };

                dbStore.getCategories = (options, caback) => {
                    if (!caback && typeof options === 'function') {
                        caback = options;
                        options = {};
                    }

                    const categories = dbStore.rawCategories;

                    if (options.id) {
                        const elementPos = categories
                            .map((x) => {
                                return x.id;
                            })
                            .indexOf(options.id);
                        if (elementPos < 0) {
                            caback('no category with that id found!');
                        }
                        const pluginsId = categories[elementPos].plugins;
                        const result = pluginsId.map((pluginName) => dbStore.filter(
                            (plugin) => {
                                return plugin.name === pluginName;
                            })
                        ).map(key => key[0]);
                        caback(null, createResponseResults(result));
                    } else {
                        var response = {};
                        async.forEach(categories, (category, caback) => {
                            response[category.id] = category.plugins.map(
                                (pluginName) => dbStore.filter(
                                    (plugin) => {
                                        return plugin.name === pluginName;
                                    }));
                            caback();
                        }, (err) => {
                            caback(err, response);
                        });
                    }

                };

                cb();

            });
        }, (cb) => {
            fs.readFile(categoryFile, 'utf8', (err, contents) => {
                dbStore.rawCategories = JSON.parse(contents);
                cb();
            });
        }
    ], () => {
        // Make _filename and save non-enumerable so that they dont mess up dbStore.length
        Object.defineProperties(dbStore, {
            '_filename': {
                enumerable: false
            },
            'save': {
                enumerable: false
            },
            'search': {
                enumerable: false
            },
            'latest': {
                enumerable: false
            },
            'getCategories': {
                enumerable: false
            },
            'rawCategories': {
                enumerable: false
            }
        });
        callback(null, dbStore);
    });

};
<commit_msg>eslint - formating changes and fix offences<commit_after>const fs = require('fs');
const async = require('async');
const queryFields = ['excerpt', 'name', 'title'];

function createResponseResults(docs) {
  return {
    docs,
    limit: docs.length,
    total: docs.length,
    start: 0,
    end: docs.length,
    page: 1,
    pages: 1
  };
}

// It instantiates a local database asynchronously.
module.exports = flatDb = (filename, categoryFile, callback) => {
  var dbStore;
  async.series([
    (cb) => {
      fs.readFile(filename, {encoding: 'utf8'}, (err, data) => {
        if (err)
          return cb(err, null);

        // Parse data
        const rawData = JSON.parse(data);
        dbStore = Object.keys(rawData).map(key => rawData[key]);

        // Adds various library-specific data handles
        dbStore._filename = filename;

        dbStore.latest = () => createResponseResults(
          dbStore.sort((a, b) => a.buildDate.localeCompare(b.buildDate)).slice(0, 10));

        dbStore.search = (query, options, caback) => {
          options = options || {};
          var
            limit = options.limit || 20,
            page = options.page || 1,
            start = (page - 1) * limit,
            end = limit * (page),
            pages, total;

          var result = dbStore;

          if (query) {
            result = result.filter((item) => {
              return queryFields.map(field => item[field].includes(query)).indexOf(true) > -1;
            });

          }
          total = result.length;
          pages = Math.floor(total / limit) || 1;
          caback(null, {
            limit,
            start,
            end,
            total,
            page,
            pages,
            docs: result.slice(start, end),
          });
        };

        dbStore.getCategories = (options, caback) => {
          if (!caback && typeof options === 'function') {
            caback = options;
            options = {};
          }

          const categories = dbStore.rawCategories;

          if (options.id) {
            const elementPos = categories
              .map((x) => {
                return x.id;
              })
              .indexOf(options.id);
            if (elementPos < 0) {
              caback('no category with that id found!');
            }
            const pluginsId = categories[elementPos].plugins;
            const result = pluginsId.map((pluginName) => dbStore.filter(
              (plugin) => {
                return plugin.name === pluginName;
              })
            ).map(key => key[0]);
            caback(null, createResponseResults(result));
          } else {
            var response = {};
            async.forEach(categories, (category, caback) => {
              response[category.id] = category.plugins.map(
                (pluginName) => dbStore.filter(
                  (plugin) => {
                    return plugin.name === pluginName;
                  }));
              caback();
            }, (err) => {
              caback(err, response);
            });
          }

        };

        cb();

      });
    }, (cb) => {
      fs.readFile(categoryFile, 'utf8', (err, contents) => {
        dbStore.rawCategories = JSON.parse(contents);
        cb();
      });
    }
  ], () => {
    // Make _filename and save non-enumerable so that they dont mess up dbStore.length
    Object.defineProperties(dbStore, {
      '_filename': {
        enumerable: false
      },
      'save': {
        enumerable: false
      },
      'search': {
        enumerable: false
      },
      'latest': {
        enumerable: false
      },
      'getCategories': {
        enumerable: false
      },
      'rawCategories': {
        enumerable: false
      }
    });
    callback(null, dbStore);
  });

};
<|endoftext|>


<commit_before>import { Meteor } from 'meteor/meteor';

import { Minutes } from './../minutes';
import { UserRoles } from './../userroles';
import { TemplateRenderer } from './../server_side_templates/TemplateRenderer';
import { DocumentGeneration } from './../documentGeneration';

Meteor.methods({
    'documentgeneration.createHTML'(minuteID) {
        //Check DocumentGeneration is enabled and user has rights to continue
        if (Meteor.settings.public.docGeneration.enabled !== true) {
            return;
        }

        if (!Meteor.userId()) {
            throw new Meteor.Error('not-authorized', 'You are not authorized to perform this action.');
        }

        let minute = new Minutes(minuteID);
        let userRoles = new UserRoles(Meteor.userId());
        if (!userRoles.isInvitedTo(minute.parentMeetingSeriesID())) {
            throw new Meteor.Error('Cannot download this minute', 'You are not invited to the meeting series.');
        }

        let topics = minute.getTopicsWithOnlyInfoItems();
        let documentHandler = {
            _topics: topics,
            _minute: minute,
            _meetingSeries: minute.parentMeetingSeries(),
            _participants: minute.getParticipants(Meteor.users),
            _informed: minute.getInformed(Meteor.users),
            _userArrayToString: function(users) {
                return users.map(function(user){
                    return user.name;
                }).join(', ');
            }
        };

        DocumentGeneration.generateResponsibleStringsForTopic(documentHandler);
        let templateData = DocumentGeneration.getDocumentData(documentHandler);

        let tmplRenderer = new TemplateRenderer('publishInfoItems', 'server_templates/email').addData('name', '');
        tmplRenderer.addDataObject(templateData);
        DocumentGeneration.addHelperForHTMLMail(tmplRenderer, documentHandler);
        return tmplRenderer.render();
    }
});
<commit_msg>Fix: Bug introduced after merge<commit_after>import { Meteor } from 'meteor/meteor';

import { Minutes } from './../minutes';
import { UserRoles } from './../userroles';
import { TemplateRenderer } from './../server_side_templates/TemplateRenderer';
import { DocumentGeneration } from './../documentGeneration';

Meteor.methods({
    'documentgeneration.createHTML'(minuteID) {
        //Check DocumentGeneration is enabled and user has rights to continue
        if (Meteor.settings.public.docGeneration.enabled !== true) {
            return;
        }

        if (!Meteor.userId()) {
            throw new Meteor.Error('not-authorized', 'You are not authorized to perform this action.');
        }

        let minute = new Minutes(minuteID);
        let userRoles = new UserRoles(Meteor.userId());
        if (!userRoles.isInvitedTo(minute.parentMeetingSeriesID())) {
            throw new Meteor.Error('Cannot download this minute', 'You are not invited to the meeting series.');
        }

        let documentHandler = {
            _topics: minute.topics,
            _minute: minute,
            _meetingSeries: minute.parentMeetingSeries(),
            _participants: minute.getParticipants(Meteor.users),
            _informed: minute.getInformed(Meteor.users),
            _userArrayToString: function(users) {
                return users.map(function(user){
                    return user.name;
                }).join(', ');
            }
        };

        DocumentGeneration.generateResponsibleStringsForTopic(documentHandler);
        let templateData = DocumentGeneration.getDocumentData(documentHandler);

        let tmplRenderer = new TemplateRenderer('publishInfoItems', 'server_templates/email').addData('name', '');
        tmplRenderer.addDataObject(templateData);
        DocumentGeneration.addHelperForHTMLMail(tmplRenderer, documentHandler);
        return tmplRenderer.render();
    }
});
<|endoftext|>


<commit_before>import React from 'react';
import {arc as _arc, pie as _pie} from 'd3-shape';

/*

interface DonutProps{
    proportion: number [0, 1]
    innerText: string
    width: number
    height: number
}

*/
export default function({
        width = 400, height = 400,
        donutWidth = 50, outerRadius = 200,
        padAngle = Math.PI/30,
        proportion,
        innerText
    }){

    const pie = _pie()
        .startAngle(2*Math.PI)
        .endAngle(0)
        .padAngle(padAngle)
        .sort( (x, y) => x === proportion ? -1 : 1);
    const arc = _arc();

    const data = [proportion, 1-proportion];
    const arcDescs = pie(data);

    return React.createElement('svg', {width, height, className: 'focus-donut'},
        React.createElement(
            'g',
            {transform: `translate(${width/2}, ${height/2})`},
            arcDescs.map(ad => {
                const d = arc(Object.assign(
                    {
                        outerRadius,
                        innerRadius: outerRadius - donutWidth
                    },
                    ad
                ))

                return React.createElement('g', {className: ['arc', ad.data === proportion ? 'highlighted' : ''].join(' ')},
                    React.createElement('path', {d})
                )
            }),
            React.createElement('g', {},
                React.createElement('text', {className: 'percentage', textAnchor: 'middle', dy: "0.1em"},
                    React.createElement('tspan', {className: 'percent', textAnchor: 'middle'}, Math.round(100*proportion)),
                    React.createElement('tspan', {textAnchor: 'middle'}, '%')
                ),
                innerText.map((t, i) => {
                    return React.createElement('text', {textAnchor: 'middle', dy: (i*1+2.5)+"em"}, t)
                })
            )

        )
    );
}
<commit_msg>Ne pas afficher le donut tant qu'on n'a pas de nombre<commit_after>import React from 'react';
import {arc as _arc, pie as _pie} from 'd3-shape';

/*

interface DonutProps{
    proportion: number [0, 1]
    innerText: string
    width: number
    height: number
}

*/
export default function({
        width = 400, height = 400,
        donutWidth = 50, outerRadius = 200,
        padAngle = Math.PI/30,
        proportion,
        innerText
    }){

    const pie = _pie()
        .startAngle(2*Math.PI)
        .endAngle(0)
        .padAngle(padAngle)
        .sort( (x, y) => x === proportion ? -1 : 1);
    const arc = _arc();

    const data = [proportion, 1-proportion];
    const arcDescs = pie(data);

    return React.createElement('svg', {width, height, className: 'focus-donut'},
        React.createElement(
            'g',
            {transform: `translate(${width/2}, ${height/2})`},
            arcDescs.map(ad => {
                const d = arc(Object.assign(
                    {
                        outerRadius,
                        innerRadius: outerRadius - donutWidth
                    },
                    ad
                ))

                return React.createElement('g', {className: ['arc', ad.data === proportion ? 'highlighted' : ''].join(' ')},
                    React.createElement('path', {d})
                )
            }),
            proportion ? React.createElement('g', {},
                React.createElement('text', {className: 'percentage', textAnchor: 'middle', dy: "0.1em"},
                    React.createElement('tspan', {className: 'percent', textAnchor: 'middle'}, Math.round(100*proportion)),
                    React.createElement('tspan', {textAnchor: 'middle'}, '%')
                ),
                innerText.map((t, i) => {
                    return React.createElement('text', {textAnchor: 'middle', dy: (i*1+2.5)+"em"}, t)
                })
            ) : undefined

        )
    );
}
<|endoftext|>


<commit_before>var SimplePeer = require('simple-peer')
var pull = require('pull-stream')
var lendStream = require('pull-lend-stream')
var limit = require('pull-limit')
var toPull = require('stream-to-pull-stream')
var toObject = require('pull-stream-function-to-object')
var debug = require('debug')
var probe = require('pull-probe')

var processorNb = 0

function idSummary (id) {
  if (id) return id.slice(0, 4)
  else return id
}

function createProcessor (node, opts) {
  var log = debug('pando:processor(' + processorNb++ + ')')
  var closed = false

  function close (err) {
    if (closed) return
    closed = true

    if (err) log('closing with error: ' + err)
    else log('closing')

    node.close()

    log('clearing report timeout')
    if (periodicReportTimeout) clearTimeout(periodicReportTimeout)

    log('closing all children')
    for (var id in children) {
      children[id].close()
    }
  }

  function handlePandoMessages (channel) {
    function parse (data) {
      var message = JSON.parse(data)
      if (message.type === 'DATA-CHANNEL-SIGNAL') {
        log('channel(' + idSummary(channel.id) + ') received DATA-CHANNEL-SIGNAL')
        channel.emit('data-channel-signal', message.signal)
      } else if (message.type === 'STATUS') {
        log('channel(' + idSummary(channel.id) + ') received STATUS')
        log(message)
        channel.emit('status', message)
      } else {
        log('channel(' + idSummary(channel.id) + ') INVALID MESSAGE: ' + data.toString())
      }
    }
    channel.on('data', parse)
  }

  function sendStatus (channel, status) {
    if (!channel) return

    log('channel(' + idSummary(channel.id) + ') sending STATUS')
    var message = {
      type: 'STATUS'
    }

    for (var p in status) {
      message[p] = status[p]
    }

    log(message)
    channel.send(JSON.stringify(message))
  }

  function sendDataChannelSignal (channel, signal) {
    log('channel(' + idSummary(channel.id) + ') sending DATA-CHANNEL-SIGNAL')
    var message = {
      type: 'DATA-CHANNEL-SIGNAL',
      signal: signal
    }

    channel.send(JSON.stringify(message))
  }

  function startProcessing () {
    log('starting processing')
    processingStarted = true
    lender.lendStream(function (err, stream) {
      if (err === true) {
        log('lendStream(true), stream already ended')
        return
      } else if (err) {
        log('lendStream(' + err + '), aborting')
        return
      }
      log('processing started')

      pull(
        stream,
        probe('processing-input'),
        pull.asyncMap(function (x, cb) {
          x = JSON.parse(x)
          if (processingEnded) {
            cb(processingEnded)
          } else {
            opts.bundle(x, cb)
          }
        }),
        pull.map(function (x) { return String(x) }),
        probe('processing-output'),
        stream
      )
    })
  }

  function periodicReport () {
    log('periodicReport every ' + opts.reportingInterval + ' ms')
    sendSummary()
    periodicReportTimeout = setTimeout(periodicReport, opts.reportingInterval)
  }

  function addChild (child) {
    childrenNb++
    if (childrenNb >= node.maxDegree) {
      // For all new connections that are not from an intermediate node
      // rejoining after a disconnection from its parent, the new child will
      // have no children.  Report a single leaf node to our parent
      // optimistically.  If the child has more, it will eventually give us a
      // status update with the exact number. This allows quickly scaling up
      // when many nodes are joining at a fast rate.
      addStatus(child.id, {
        nbLeafNodes: 1,
        childrenNb: 0
      })
      sendSummary()
    }

    children[child.id] = child
  }

  if (!node) {
    throw new Error('Invalid node')
  }

  opts = opts || {}

  if (!opts.hasOwnProperty('startProcessing')) {
    opts.startProcessing = true
  }

  if (!opts.hasOwnProperty('initialChildLimit')) {
    opts.initialChildLimit = 1
  }

  if (!opts.hasOwnProperty('reportingInterval')) {
    opts.reportingInterval = 3 * 1000 // ms
  }

  opts.bundle = opts.bundle || function (x, cb) {
    log('computing ' + x + ' squared')
    setTimeout(function () {
      var r = JSON.stringify(x * x)
      log('computed ' + r)
      cb(null, r)
    }, 100)
  }

  log('creating processor with options')
  log(opts)

  var periodicReportTimeout = null
  var processingEnded = false
  var processingStarted = false
  var parent = null
  var unprocessedInputs = 0

  var lender = lendStream()

  node.on('parent-connect', function (controlChannel) {
    log('connected to parent')
    handlePandoMessages(controlChannel)
    parent = controlChannel
    controlChannel.on('data-channel-signal', function (signal) {
      dataChannel.signal(signal)
    })
    controlChannel.on('status', function (status) {
      log('Unexpected status message from parent')
    })
    controlChannel.on('close', function () {
      log('parent control channel closed')
      close()
    })
    controlChannel.on('error', function (err) {
      log('parent control channel failed with error: ' + err)
      close()
    })

    sendSummary()

    // 1. open data channel
    var dataChannel = new SimplePeer(node.peerOpts)
    dataChannel.on('signal', function (data) {
      sendDataChannelSignal(controlChannel, data)
    })
      .on('connect', function () {
        node.emit('ready')

        var pullDataChannel = toPull.duplex(dataChannel)
        var s = pullDataChannel

        pull(
          s,
          pull.through(function () { unprocessedInputs++ }),
          lender,
          pull.through(function () { unprocessedInputs-- }),
          s
        )

        log('connected to parent data channel')
        startProcessing()
      })
      .on('close', function () {
        log('parent data channel closed')
        close()
      })
      .on('error', function (err) {
        log('parent data channel failed with error: ' + err)
        close()
      })

    node.once('close', function () {
      log('destroying parent channel')
      dataChannel.destroy()
    })
  })

  var latestStatus = {}
  var childrenNb = 0
  var children = {}

  function addStatus (id, status) {
    latestStatus[id] = status
  }

  function sendSummary () {
    var summary = {
      id: node.id,
      unprocessedInputs: unprocessedInputs,
      processing: (processingStarted && !processingEnded),
      childrenNb: childrenNb,
      nbLeafNodes: (processingEnded) ? 0 : 1,
      lendSteamState: lender._state(),
      limits: {},
      childrenUnprocessedInputs: {}
    }

    for (var s in latestStatus) {
      var n = latestStatus[s].nbLeafNodes
      var c = latestStatus[s].childrenNb
      summary.nbLeafNodes += n
      summary.childrenNb += c
      summary.limits[latestStatus[s].id] = latestStatus[s].limit
      summary.childrenUnprocessedInputs[latestStatus[s].id] = latestStatus[s].unprocessedInputs
    }

    log('sendSummary: ' + JSON.stringify(summary))
    node.emit('status', summary)
    if (parent) {
      sendStatus(parent, summary)
    }
  }

  function removeChild (child) {
    childrenNb--
    if (latestStatus[child.id]) {
      delete latestStatus[child.id]
    }

    delete children[child.id]

    // Restart processing when we are not
    // coordinating children
    if (childrenNb === 0 && opts.startProcessing) {
      processingEnded = false
      startProcessing()
    }

    child.destroy()
  }

  node.on('child-connect', function (child) {
    log('connected to child(' + idSummary(child.id) + ')')
    addChild(child)
    handlePandoMessages(child)
    child.on('data-channel-signal', function (signal) {
      if (dataChannel) {
        dataChannel.signal(signal)
      } else {
        log('WARNING: Missed data-channel-signal from child(' + idSummary(child.id) + ')')
      }
    })
    child.on('status', function (status) {
      if (limitedChannel) {
        // Ensure each leaf node (which performs the computations) has at least
        // maxDegree tasks to work on so that they can dispatch them quickly if
        // maxDegree children join. If maxDegree children join under them, we
        // will receive a status update with the new number of leaf nodes and
        // the limit will be updated accordingly.
        var limit = (status.nbLeafNodes) * node.maxDegree

        if (limit > 0) {
          status.limit = limit
          log('updating child(' + idSummary(child.id) + ') limit to ' + status.limit)
          limitedChannel.updateLimit(status.limit)
        }
      }
      addStatus(child.id, status)
    })
    child.on('close', function () {
      log('child(' + idSummary(child.id) + ') control channel closed')
      if (limitedChannel) limitedChannel.source(true, function () {})
      removeChild(child)
      if (dataChannel) dataChannel.destroy()
    })
    child.on('error', function (err) {
      log('child(' + idSummary(child.id) + ') control channel failed with error: ' + err)
      if (limitedChannel) limitedChannel.source(true, function () {})
      removeChild(child)
      if (dataChannel) dataChannel.destroy()
    })

    var limitedChannel = null
    var peerOpts = {}
    for (var p in node.peerOpts) {
      peerOpts[p] = node.peerOpts[p]
    }
    peerOpts.initiator = true
    var dataChannel = new SimplePeer(peerOpts)
    dataChannel
      .on('signal', function (data) {
        sendDataChannelSignal(child, data)
      })
      .on('connect', function () {
        log('connected to child(' + idSummary(child.id) + ') data channel')
        log('stopping processing')
        processingEnded = true

        var pullDataChannel = toPull.duplex(dataChannel)
        limitedChannel = limit(pullDataChannel, opts.initialChildLimit)

        var unprocessedInputs = 0

        lender.lendStream(function (err, subStream) {
          if (err) {
            log('lendStream(' + err + ')')
            if (!node.parent) { log('parent not connected yet') }
            throw err
          }

          log('child(' + idSummary(child.id) + ') subStream opened')
          pull(
            subStream,
            probe('pando:child:input'),
            pull.through(function () {
              unprocessedInputs++
              if (child.id && latestStatus[child.id]) {
                latestStatus[child.id].unprocessedInputs = unprocessedInputs
              }
            }),
            limitedChannel,
            pull.through(function () {
              unprocessedInputs--
              if (child.id && latestStatus[child.id]) {
                latestStatus[child.id].unprocessedInputs = unprocessedInputs
              }
            }),
            probe('pando:child:result'),
            subStream
          )
        })
      })
      .on('close', function () {
        log('child(' + idSummary(child.id) + ') data channel closed')
        if (limitedChannel) limitedChannel.source(true, function () {})
        child.destroy()
      })
      .on('error', function (err) {
        log('child(' + idSummary(child.id) + ') data channel failed with error: ' + err)
        if (limitedChannel) limitedChannel.source(err, function () {})
        child.destroy()
      })
    node.once('close', function () {
      dataChannel.destroy()
    })
  })

  node.on('status', function (summary) {
    log('status summary: ' + JSON.stringify(summary))
    if (limitedLender && summary.nbLeafNodes > 0) {
      var updatedLimit = summary.nbLeafNodes * node.maxDegree
      log('updating processor limit to ' + updatedLimit)
      limitedLender.updateLimit(updatedLimit)
    }
  })

  node.on('close', close)
  node.on('error', close)

  // That should not be necessary but there is a bug
  // that triggers an infinite number of reads from time
  // to time
  var limitedLender = limit(lender, 10)

  var processor = toObject(pull(
    pull.map(function (x) { return JSON.stringify(x) }),
    pull.through(function () { unprocessedInputs++ }),
    limitedLender,
    pull.through(function () { unprocessedInputs-- }),
    pull.map(function (x) { return JSON.parse(x) })
  ))

  node.sink = processor.sink.bind(lender)
  var source = processor.source.bind(lender)

  node.source = function (abort, cb) {
    if (opts.startProcessing && !processingStarted) startProcessing()
    source(abort, cb)
  }

  periodicReport()
  return node
}

module.exports = createProcessor
<commit_msg>Removed throw and fixed typo<commit_after>var SimplePeer = require('simple-peer')
var pull = require('pull-stream')
var lendStream = require('pull-lend-stream')
var limit = require('pull-limit')
var toPull = require('stream-to-pull-stream')
var toObject = require('pull-stream-function-to-object')
var debug = require('debug')
var probe = require('pull-probe')

var processorNb = 0

function idSummary (id) {
  if (id) return id.slice(0, 4)
  else return id
}

function createProcessor (node, opts) {
  var log = debug('pando:processor(' + processorNb++ + ')')
  var closed = false

  function close (err) {
    if (closed) return
    closed = true

    if (err) log('closing with error: ' + err)
    else log('closing')

    node.close()

    log('clearing report timeout')
    if (periodicReportTimeout) clearTimeout(periodicReportTimeout)

    log('closing all children')
    for (var id in children) {
      children[id].close()
    }
  }

  function handlePandoMessages (channel) {
    function parse (data) {
      var message = JSON.parse(data)
      if (message.type === 'DATA-CHANNEL-SIGNAL') {
        log('channel(' + idSummary(channel.id) + ') received DATA-CHANNEL-SIGNAL')
        channel.emit('data-channel-signal', message.signal)
      } else if (message.type === 'STATUS') {
        log('channel(' + idSummary(channel.id) + ') received STATUS')
        log(message)
        channel.emit('status', message)
      } else {
        log('channel(' + idSummary(channel.id) + ') INVALID MESSAGE: ' + data.toString())
      }
    }
    channel.on('data', parse)
  }

  function sendStatus (channel, status) {
    if (!channel) return

    log('channel(' + idSummary(channel.id) + ') sending STATUS')
    var message = {
      type: 'STATUS'
    }

    for (var p in status) {
      message[p] = status[p]
    }

    log(message)
    channel.send(JSON.stringify(message))
  }

  function sendDataChannelSignal (channel, signal) {
    log('channel(' + idSummary(channel.id) + ') sending DATA-CHANNEL-SIGNAL')
    var message = {
      type: 'DATA-CHANNEL-SIGNAL',
      signal: signal
    }

    channel.send(JSON.stringify(message))
  }

  function startProcessing () {
    log('starting processing')
    processingStarted = true
    lender.lendStream(function (err, stream) {
      if (err === true) {
        log('lendStream(true), stream already ended')
        return
      } else if (err) {
        log('lendStream(' + err + '), aborting')
        return
      }
      log('processing started')

      pull(
        stream,
        probe('processing-input'),
        pull.asyncMap(function (x, cb) {
          x = JSON.parse(x)
          if (processingEnded) {
            cb(processingEnded)
          } else {
            opts.bundle(x, cb)
          }
        }),
        pull.map(function (x) { return String(x) }),
        probe('processing-output'),
        stream
      )
    })
  }

  function periodicReport () {
    log('periodicReport every ' + opts.reportingInterval + ' ms')
    sendSummary()
    periodicReportTimeout = setTimeout(periodicReport, opts.reportingInterval)
  }

  function addChild (child) {
    childrenNb++
    if (childrenNb >= node.maxDegree) {
      // For all new connections that are not from an intermediate node
      // rejoining after a disconnection from its parent, the new child will
      // have no children.  Report a single leaf node to our parent
      // optimistically.  If the child has more, it will eventually give us a
      // status update with the exact number. This allows quickly scaling up
      // when many nodes are joining at a fast rate.
      addStatus(child.id, {
        nbLeafNodes: 1,
        childrenNb: 0
      })
      sendSummary()
    }

    children[child.id] = child
  }

  if (!node) {
    throw new Error('Invalid node')
  }

  opts = opts || {}

  if (!opts.hasOwnProperty('startProcessing')) {
    opts.startProcessing = true
  }

  if (!opts.hasOwnProperty('initialChildLimit')) {
    opts.initialChildLimit = 1
  }

  if (!opts.hasOwnProperty('reportingInterval')) {
    opts.reportingInterval = 3 * 1000 // ms
  }

  opts.bundle = opts.bundle || function (x, cb) {
    log('computing ' + x + ' squared')
    setTimeout(function () {
      var r = JSON.stringify(x * x)
      log('computed ' + r)
      cb(null, r)
    }, 100)
  }

  log('creating processor with options')
  log(opts)

  var periodicReportTimeout = null
  var processingEnded = false
  var processingStarted = false
  var parent = null
  var unprocessedInputs = 0

  var lender = lendStream()

  node.on('parent-connect', function (controlChannel) {
    log('connected to parent')
    handlePandoMessages(controlChannel)
    parent = controlChannel
    controlChannel.on('data-channel-signal', function (signal) {
      dataChannel.signal(signal)
    })
    controlChannel.on('status', function (status) {
      log('Unexpected status message from parent')
    })
    controlChannel.on('close', function () {
      log('parent control channel closed')
      close()
    })
    controlChannel.on('error', function (err) {
      log('parent control channel failed with error: ' + err)
      close()
    })

    sendSummary()

    // 1. open data channel
    var dataChannel = new SimplePeer(node.peerOpts)
    dataChannel.on('signal', function (data) {
      sendDataChannelSignal(controlChannel, data)
    })
      .on('connect', function () {
        node.emit('ready')

        var pullDataChannel = toPull.duplex(dataChannel)
        var s = pullDataChannel

        pull(
          s,
          pull.through(function () { unprocessedInputs++ }),
          lender,
          pull.through(function () { unprocessedInputs-- }),
          s
        )

        log('connected to parent data channel')
        startProcessing()
      })
      .on('close', function () {
        log('parent data channel closed')
        close()
      })
      .on('error', function (err) {
        log('parent data channel failed with error: ' + err)
        close()
      })

    node.once('close', function () {
      log('destroying parent channel')
      dataChannel.destroy()
    })
  })

  var latestStatus = {}
  var childrenNb = 0
  var children = {}

  function addStatus (id, status) {
    latestStatus[id] = status
  }

  function sendSummary () {
    var summary = {
      id: node.id,
      unprocessedInputs: unprocessedInputs,
      processing: (processingStarted && !processingEnded),
      childrenNb: childrenNb,
      nbLeafNodes: (processingEnded) ? 0 : 1,
      lendStreamState: lender._state(),
      limits: {},
      childrenUnprocessedInputs: {}
    }

    for (var s in latestStatus) {
      var n = latestStatus[s].nbLeafNodes
      var c = latestStatus[s].childrenNb
      summary.nbLeafNodes += n
      summary.childrenNb += c
      summary.limits[latestStatus[s].id] = latestStatus[s].limit
      summary.childrenUnprocessedInputs[latestStatus[s].id] = latestStatus[s].unprocessedInputs
    }

    log('sendSummary: ' + JSON.stringify(summary))
    node.emit('status', summary)
    if (parent) {
      sendStatus(parent, summary)
    }
  }

  function removeChild (child) {
    childrenNb--
    if (latestStatus[child.id]) {
      delete latestStatus[child.id]
    }

    delete children[child.id]

    // Restart processing when we are not
    // coordinating children
    if (childrenNb === 0 && opts.startProcessing) {
      processingEnded = false
      startProcessing()
    }

    child.destroy()
  }

  node.on('child-connect', function (child) {
    log('connected to child(' + idSummary(child.id) + ')')
    addChild(child)
    handlePandoMessages(child)
    child.on('data-channel-signal', function (signal) {
      if (dataChannel) {
        dataChannel.signal(signal)
      } else {
        log('WARNING: Missed data-channel-signal from child(' + idSummary(child.id) + ')')
      }
    })
    child.on('status', function (status) {
      if (limitedChannel) {
        // Ensure each leaf node (which performs the computations) has at least
        // maxDegree tasks to work on so that they can dispatch them quickly if
        // maxDegree children join. If maxDegree children join under them, we
        // will receive a status update with the new number of leaf nodes and
        // the limit will be updated accordingly.
        var limit = (status.nbLeafNodes) * node.maxDegree

        if (limit > 0) {
          status.limit = limit
          log('updating child(' + idSummary(child.id) + ') limit to ' + status.limit)
          limitedChannel.updateLimit(status.limit)
        }
      }
      addStatus(child.id, status)
    })
    child.on('close', function () {
      log('child(' + idSummary(child.id) + ') control channel closed')
      if (limitedChannel) limitedChannel.source(true, function () {})
      removeChild(child)
      if (dataChannel) dataChannel.destroy()
    })
    child.on('error', function (err) {
      log('child(' + idSummary(child.id) + ') control channel failed with error: ' + err)
      if (limitedChannel) limitedChannel.source(true, function () {})
      removeChild(child)
      if (dataChannel) dataChannel.destroy()
    })

    var limitedChannel = null
    var peerOpts = {}
    for (var p in node.peerOpts) {
      peerOpts[p] = node.peerOpts[p]
    }
    peerOpts.initiator = true
    var dataChannel = new SimplePeer(peerOpts)
    dataChannel
      .on('signal', function (data) {
        sendDataChannelSignal(child, data)
      })
      .on('connect', function () {
        log('connected to child(' + idSummary(child.id) + ') data channel')
        log('stopping processing')
        processingEnded = true

        var pullDataChannel = toPull.duplex(dataChannel)
        limitedChannel = limit(pullDataChannel, opts.initialChildLimit)

        var unprocessedInputs = 0

        lender.lendStream(function (err, subStream) {
          if (err) {
            log('lendStream(' + err + ')')
            if (!node.parent) { log('parent not connected yet') }
            return close(err)
          }

          log('child(' + idSummary(child.id) + ') subStream opened')
          pull(
            subStream,
            probe('pando:child:input'),
            pull.through(function () {
              unprocessedInputs++
              if (child.id && latestStatus[child.id]) {
                latestStatus[child.id].unprocessedInputs = unprocessedInputs
              }
            }),
            limitedChannel,
            pull.through(function () {
              unprocessedInputs--
              if (child.id && latestStatus[child.id]) {
                latestStatus[child.id].unprocessedInputs = unprocessedInputs
              }
            }),
            probe('pando:child:result'),
            subStream
          )
        })
      })
      .on('close', function () {
        log('child(' + idSummary(child.id) + ') data channel closed')
        if (limitedChannel) limitedChannel.source(true, function () {})
        child.destroy()
      })
      .on('error', function (err) {
        log('child(' + idSummary(child.id) + ') data channel failed with error: ' + err)
        if (limitedChannel) limitedChannel.source(err, function () {})
        child.destroy()
      })
    node.once('close', function () {
      dataChannel.destroy()
    })
  })

  node.on('status', function (summary) {
    log('status summary: ' + JSON.stringify(summary))
    if (limitedLender && summary.nbLeafNodes > 0) {
      var updatedLimit = summary.nbLeafNodes * node.maxDegree
      log('updating processor limit to ' + updatedLimit)
      limitedLender.updateLimit(updatedLimit)
    }
  })

  node.on('close', close)
  node.on('error', close)

  // That should not be necessary but there is a bug
  // that triggers an infinite number of reads from time
  // to time
  var limitedLender = limit(lender, 10)

  var processor = toObject(pull(
    pull.map(function (x) { return JSON.stringify(x) }),
    pull.through(function () { unprocessedInputs++ }),
    limitedLender,
    pull.through(function () { unprocessedInputs-- }),
    pull.map(function (x) { return JSON.parse(x) })
  ))

  node.sink = processor.sink.bind(lender)
  var source = processor.source.bind(lender)

  node.source = function (abort, cb) {
    if (opts.startProcessing && !processingStarted) startProcessing()
    source(abort, cb)
  }

  periodicReport()
  return node
}

module.exports = createProcessor
<|endoftext|>