<commit_before>var utils = require('../utils');

module.exports = require('./widget').extend()
  .prop('width')
  .default(400)

  .prop('colspan')
  .default(4)

  .prop('rowspan')
  .default(4)

  .prop('height')
  .default(200)

  .prop('colors')

  .prop('title')
  .set(d3.functor)
  .default(function(d) { return d.title; })

  .prop('metrics')
  .set(d3.functor)
  .default(function(d) { return d.metrics; })

  .prop('key')
  .set(d3.functor)
  .default(function(d, i) { return i; })

  .prop('metricTitle')
  .set(d3.functor)
  .default(function(d) { return d.title; })

  .prop('value')
  .set(d3.functor)
  .default(function(d) { return d.value; })

  .prop('margin')
  .default({
    top: 20,
    left: 20,
    right: 20,
    bottom: 20
  })

  .prop('innerRadius')
  .set(d3.functor)
  .default(function(r) { return 0.35 * r; })

  .prop('valueFormat')
  .default(d3.format(',2s'))

  .prop('percentFormat')
  .default(d3.format('.0%'))

  .init(function() {
    this.colors(d3.scale.category10());
  })

  .meth(function normalize(el) {
    var self = this;
    var node = el.node();

    el.datum(function(d, i) {
      return {
        title: self.title().call(node, d, i),
        metrics: self.metrics()
          .call(node, d, i)
          .map(metric)
      };
    });

    function metric(d, i) {
      var key = self.key()
        .call(node, d, i)
        .toString();

      return {
        key: key,
        color: self.colors()(key),
        title: self.metricTitle().call(node, d, i),
        value: self.value().call(node, d, i)
      };
    }

    var sum = d3.sum(el.datum().metrics, function(d) { return d.value; });
    el.datum().metrics.forEach(function(d) {
        return d.percent = d.value / sum;
    });
  })

  .enter(function(el) {
    el.attr('class', 'pie widget');

    el.append('div')
      .attr('class', 'title');

    el.append('div')
      .attr('class', 'chart');

    el.append('div')
      .attr('class', 'legend');
  })

  .draw(function(el) {
    this.normalize(el);

    el.select('.widget .title')
      .text(function(d) { return d.title; });

    el.select('.legend')
      .call(legend(this));

    el.select('.chart')
      .style('height', el.style('min-height'))
      .call(chart(this));
  });

var chart = require('../view').extend()
  .prop('widget')

  .init(function(widget) {
    this.widget(widget);
  })

  .enter(function(el) {
    el.append('svg')
      .append('g');
  })

  .draw(function(el) {
    var dims = utils.box()
      .margin(this.widget().margin())
      .width(parseInt(el.style('width')))
      .height(parseInt(el.style('height')))
      .calc();

    var radius = Math.min(dims.innerWidth, dims.innerHeight) / 2;

    var svg = el.select('svg')
      .attr('width', dims.width)
      .attr('height', dims.height)
      .select('g')
        .attr('transform', utils.translate(dims.margin.left, dims.margin.top));

    var arc = d3.svg.arc()
      .innerRadius(this.widget().innerRadius()(radius))
      .outerRadius(radius);

    var layout = d3.layout.pie()
      .value(function(d) { return d.value; });

    var slice = svg.selectAll('.slice')
      .data(function(d) { return layout(d.metrics); },
            function(d) { return d.data.key; });

    slice.enter().append('g')
      .attr('class', 'slice')
      .append('path');

    slice
      .attr('transform', utils.translate(radius, radius));

    slice.select('path')
      .attr('d', arc)
      .style('fill', function(d) { return d.data.color; });

    slice.exit()
      .remove();
  });

var legend = require('../view').extend()
  .prop('widget')

  .init(function(widget) {
    this.widget(widget);
  })

  .enter(function(el) {
    el.append('table')
      .attr('class', 'table');
  })

  .draw(function(el) {
    var valueFormat = this.widget().valueFormat();
    var percentFormat = this.widget().percentFormat();

    var metric = el.select('.table').selectAll('.metric')
      .data(function(d) { return d.metrics; },
            function(d) { return d.key; });

    var enterMetric = metric.enter().append('tr')
      .attr('class', 'metric');

    enterMetric.append('td')
      .attr('class', 'swatch');

    enterMetric.append('td')
      .attr('class', 'title');

    enterMetric.append('td')
      .attr('class', 'percent');

    enterMetric.append('td')
      .attr('class', 'value');

    metric.select('.swatch')
      .style('background', function(d) { return d.color; });

    metric.select('.title')
      .text(function(d) { return d.title; });

    metric.select('.percent')
      .text(function(d) { return percentFormat(d.percent) });

    metric.select('.value')
      .text(function(d) { return valueFormat(d.value) });

    metric.exit()
      .remove();
  });
<commit_msg>jshint fixes<commit_after>var utils = require('../utils');

module.exports = require('./widget').extend()
  .prop('width')
  .default(400)

  .prop('colspan')
  .default(4)

  .prop('rowspan')
  .default(4)

  .prop('height')
  .default(200)

  .prop('colors')

  .prop('title')
  .set(d3.functor)
  .default(function(d) { return d.title; })

  .prop('metrics')
  .set(d3.functor)
  .default(function(d) { return d.metrics; })

  .prop('key')
  .set(d3.functor)
  .default(function(d, i) { return i; })

  .prop('metricTitle')
  .set(d3.functor)
  .default(function(d) { return d.title; })

  .prop('value')
  .set(d3.functor)
  .default(function(d) { return d.value; })

  .prop('margin')
  .default({
    top: 20,
    left: 20,
    right: 20,
    bottom: 20
  })

  .prop('innerRadius')
  .set(d3.functor)
  .default(function(r) { return 0.35 * r; })

  .prop('valueFormat')
  .default(d3.format(',2s'))

  .prop('percentFormat')
  .default(d3.format('.0%'))

  .init(function() {
    this.colors(d3.scale.category10());
  })

  .meth(function normalize(el) {
    var self = this;
    var node = el.node();

    el.datum(function(d, i) {
      return {
        title: self.title().call(node, d, i),
        metrics: self.metrics()
          .call(node, d, i)
          .map(metric)
      };
    });

    function metric(d, i) {
      var key = self.key()
        .call(node, d, i)
        .toString();

      return {
        key: key,
        color: self.colors()(key),
        title: self.metricTitle().call(node, d, i),
        value: self.value().call(node, d, i)
      };
    }

    var sum = d3.sum(el.datum().metrics, function(d) { return d.value; });
    el.datum().metrics.forEach(function(d) { d.percent = d.value / sum; });
  })

  .enter(function(el) {
    el.attr('class', 'pie widget');

    el.append('div')
      .attr('class', 'title');

    el.append('div')
      .attr('class', 'chart');

    el.append('div')
      .attr('class', 'legend');
  })

  .draw(function(el) {
    this.normalize(el);

    el.select('.widget .title')
      .text(function(d) { return d.title; });

    el.select('.legend')
      .call(legend(this));

    el.select('.chart')
      .style('height', el.style('min-height'))
      .call(chart(this));
  });

var chart = require('../view').extend()
  .prop('widget')

  .init(function(widget) {
    this.widget(widget);
  })

  .enter(function(el) {
    el.append('svg')
      .append('g');
  })

  .draw(function(el) {
    var dims = utils.box()
      .margin(this.widget().margin())
      .width(parseInt(el.style('width')))
      .height(parseInt(el.style('height')))
      .calc();

    var radius = Math.min(dims.innerWidth, dims.innerHeight) / 2;

    var svg = el.select('svg')
      .attr('width', dims.width)
      .attr('height', dims.height)
      .select('g')
        .attr('transform', utils.translate(dims.margin.left, dims.margin.top));

    var arc = d3.svg.arc()
      .innerRadius(this.widget().innerRadius()(radius))
      .outerRadius(radius);

    var layout = d3.layout.pie()
      .value(function(d) { return d.value; });

    var slice = svg.selectAll('.slice')
      .data(function(d) { return layout(d.metrics); },
            function(d) { return d.data.key; });

    slice.enter().append('g')
      .attr('class', 'slice')
      .append('path');

    slice
      .attr('transform', utils.translate(radius, radius));

    slice.select('path')
      .attr('d', arc)
      .style('fill', function(d) { return d.data.color; });

    slice.exit()
      .remove();
  });

var legend = require('../view').extend()
  .prop('widget')

  .init(function(widget) {
    this.widget(widget);
  })

  .enter(function(el) {
    el.append('table')
      .attr('class', 'table');
  })

  .draw(function(el) {
    var valueFormat = this.widget().valueFormat();
    var percentFormat = this.widget().percentFormat();

    var metric = el.select('.table').selectAll('.metric')
      .data(function(d) { return d.metrics; },
            function(d) { return d.key; });

    var enterMetric = metric.enter().append('tr')
      .attr('class', 'metric');

    enterMetric.append('td')
      .attr('class', 'swatch');

    enterMetric.append('td')
      .attr('class', 'title');

    enterMetric.append('td')
      .attr('class', 'percent');

    enterMetric.append('td')
      .attr('class', 'value');

    metric.select('.swatch')
      .style('background', function(d) { return d.color; });

    metric.select('.title')
      .text(function(d) { return d.title; });

    metric.select('.percent')
      .text(function(d) { return percentFormat(d.percent); });

    metric.select('.value')
      .text(function(d) { return valueFormat(d.value); });

    metric.exit()
      .remove();
  });
<|endoftext|>


<commit_before>'use strict'
const {custom = Symbol('unavailable: custom')} = require('util').inspect
const stringify = require('./stringify.js')
const {assign, defineProperty} = Object
const {iterator, toStringTag} = Symbol

class SearchTree extends Map {
  constructor (elements = []) {
    super()
    elements.forEach(x => this.set(...x))
  }

  has ([first, ...rest]) {
    const spval = super.get(first)

    return Boolean(spval && (
      rest.length
        ? spval.subtree.has(rest)
        : spval.exists
    ))
  }

  get ([first, ...rest]) {
    const spval = super.get(first)

    return spval
      ? (rest.length
        ? spval.subtree.get(rest)
        : spval.value
      )
      : undefined
  }

  set ([first, ...rest], value) {
    const spval = super.get(first)

    if (spval) {
      if (rest.length) {
        spval.subtree.set(rest, value)
      } else {
        const {subtree = new SearchTree()} = spval
        assign(spval, {subtree, exists: true, value})
      }
    } else {
      const {length} = rest
      const subtree = new SearchTree(length ? [[rest, value]] : [])
      super.set(first, {subtree, exists: !length, value, __proto__: null})
    }

    return this
  }

  delete ([first, ...rest]) {
    const spval = super.get(first)

    if (!spval || !spval.exists) return false

    const {subtree} = spval

    if (rest.length) {
      if (subtree) {
        subtree.delete(rest)
      } else {
        super.delete(first)
      }
    } else {
      if (subtree) {
        spval.exists = false
      } else {
        super.delete(first)
      }
    }

    return true
  }

  clear () {
    super.clear()
    return this
  }

  get size () {
    let sz = 0

    for (const {1: spval} of super.entries()) {
      if (!spval) continue

      const {subtree, exists} = spval
      if (!subtree) continue

      sz += subtree.size + (exists ? 1 : 0)
    }

    return sz
  }

  * entries () {
    for (const [keyel, spval] of super.entries()) {
      if (!spval) continue

      const {subtree, exists, value} = spval

      if (exists) yield [keyel, value]

      if (subtree) {
        for (const [rest, value] of subtree) {
          yield [[keyel, ...rest], value]
        }
      }
    }
  }

  [iterator] () {
    return this.entries()
  }

  toString ({
    prefix = `${this[toStringTag]}\n`,

    suffix = '',

    arrow = ' => ',

    delimiter = ',\n  ',

    brackets: [
      openBracket = '{ ',
      closeBracket = ' }'
    ] = []
  } = {}) {
    const middle = Array.from(this)
      .map(x => x.map(stringify).join(arrow))
      .join(delimiter)

    return prefix + openBracket + middle + closeBracket + suffix
  }

  get [custom] () {
    return this.toString()
  }

  _showInternalStructure () {
    return new Map(Array.from(super.entries())
      .map(([keyel, spval]) => {
        if (!spval) return [keyel, spval]

        const {subtree, exists, value} = spval

        return [
          keyel,
          subtree
            ? {
              subtree: subtree._showInternalStructure(),
              exists,
              value
            }
            : spval
        ]
      })
    )
  }

  get _interalStructure () {
    return this._showInternalStructure()
  }

  remove (key) {
    this.remove(key)
    return this
  }

  get length () {
    return this.size
  }

  get count () {
    return this.size
  }

  static get name () {
    return 'SearchTree'
  }

  static toString () {
    return this[toStringTag]
  }

  static create (...args) {
    return new this(...args)
  }

  static get new () {
    return this.create()
  }
}

const defconst = (object, key, value, enumerable = false) =>
  defineProperty(object, key, {value, enumerable, writable: false, configurable: false})

const {prototype} = SearchTree
defconst(prototype, toStringTag, 'SearchTree')
defconst(SearchTree, toStringTag, 'class SearchTree extends Map')

module.exports = SearchTree
<commit_msg>Fix 'delete'<commit_after>'use strict'
const {custom = Symbol('unavailable: custom')} = require('util').inspect
const stringify = require('./stringify.js')
const {assign, defineProperty} = Object
const {iterator, toStringTag} = Symbol

class SearchTree extends Map {
  constructor (elements = []) {
    super()
    elements.forEach(x => this.set(...x))
  }

  has ([first, ...rest]) {
    const spval = super.get(first)

    return Boolean(spval && (
      rest.length
        ? spval.subtree.has(rest)
        : spval.exists
    ))
  }

  get ([first, ...rest]) {
    const spval = super.get(first)

    return spval
      ? (rest.length
        ? spval.subtree.get(rest)
        : spval.value
      )
      : undefined
  }

  set ([first, ...rest], value) {
    const spval = super.get(first)

    if (spval) {
      if (rest.length) {
        spval.subtree.set(rest, value)
      } else {
        const {subtree = new SearchTree()} = spval
        assign(spval, {subtree, exists: true, value})
      }
    } else {
      const {length} = rest
      const subtree = new SearchTree(length ? [[rest, value]] : [])
      super.set(first, {subtree, exists: !length, value, __proto__: null})
    }

    return this
  }

  delete ([first, ...rest]) {
    const spval = super.get(first)

    if (!spval) return false

    const {subtree} = spval

    if (rest.length) {
      if (subtree) {
        return subtree.delete(rest)
      } else {
        super.delete(first)
        return true
      }
    } else {
      if (subtree) {
        const {exists} = spval
        spval.exists = false
        return exists
      } else {
        super.delete(first)
        return true
      }
    }
  }

  clear () {
    super.clear()
    return this
  }

  get size () {
    let sz = 0

    for (const {1: spval} of super.entries()) {
      if (!spval) continue

      const {subtree, exists} = spval
      if (!subtree) continue

      sz += subtree.size + (exists ? 1 : 0)
    }

    return sz
  }

  * entries () {
    for (const [keyel, spval] of super.entries()) {
      if (!spval) continue

      const {subtree, exists, value} = spval

      if (exists) yield [keyel, value]

      if (subtree) {
        for (const [rest, value] of subtree) {
          yield [[keyel, ...rest], value]
        }
      }
    }
  }

  [iterator] () {
    return this.entries()
  }

  toString ({
    prefix = `${this[toStringTag]}\n`,

    suffix = '',

    arrow = ' => ',

    delimiter = ',\n  ',

    brackets: [
      openBracket = '{ ',
      closeBracket = ' }'
    ] = []
  } = {}) {
    const middle = Array.from(this)
      .map(x => x.map(stringify).join(arrow))
      .join(delimiter)

    return prefix + openBracket + middle + closeBracket + suffix
  }

  get [custom] () {
    return this.toString()
  }

  _showInternalStructure () {
    return new Map(Array.from(super.entries())
      .map(([keyel, spval]) => {
        if (!spval) return [keyel, spval]

        const {subtree, exists, value} = spval

        return [
          keyel,
          subtree
            ? {
              subtree: subtree._showInternalStructure(),
              exists,
              value
            }
            : spval
        ]
      })
    )
  }

  get _interalStructure () {
    return this._showInternalStructure()
  }

  remove (key) {
    this.remove(key)
    return this
  }

  get length () {
    return this.size
  }

  get count () {
    return this.size
  }

  static get name () {
    return 'SearchTree'
  }

  static toString () {
    return this[toStringTag]
  }

  static create (...args) {
    return new this(...args)
  }

  static get new () {
    return this.create()
  }
}

const defconst = (object, key, value, enumerable = false) =>
  defineProperty(object, key, {value, enumerable, writable: false, configurable: false})

const {prototype} = SearchTree
defconst(prototype, toStringTag, 'SearchTree')
defconst(SearchTree, toStringTag, 'class SearchTree extends Map')

module.exports = SearchTree
<|endoftext|>


<commit_before>class LRU {
  constructor(crowi) {
    this.crowi = crowi
    this.max = 10
    this.client = null

    if (this.crowi.redisOpts) {
      const redis = require('redis')
      const redisClient = redis.createClient(this.crowi.redisOpts)
      this.client = redisClient
    }
  }

  removeByRange(namespace, max) {
    if (this.client) {
      return new Promise((resolve, reject) => {
        this.client.zremrangebyrank(namespace, 0, max, (err, response) => {
          if (err) reject(err)
          resolve(response)
        })
      })
    }
  }

  async add(namespace, key) {
    if (this.client) {
      await this.removeByRange(namespace, -this.max - 1)
      return new Promise((resolve, reject) => {
        this.client.zadd(namespace, Date.now(), key, (err, response) => {
          if (err) reject(err)
          resolve(response)
        })
      })
    }
  }

  range(namespace, limit = 0) {
    if (this.client) {
      return new Promise((resolve, reject) => {
        this.client.zrevrange(namespace, 0, limit - 1, (err, response) => {
          if (err) reject(err)
          resolve(response)
        })
      })
    }
  }

  get(namespace, limit) {
    if (this.client) {
      return this.range(namespace, limit)
    }
  }
}

module.exports = LRU
<commit_msg>Reuse a connection<commit_after>class LRU {
  constructor(crowi) {
    this.crowi = crowi
    this.max = 10
    this.client = null

    if (this.crowi && this.crowi.redis) {
      this.client = this.crowi.redis
    }
  }

  removeByRange(namespace, max) {
    if (this.client) {
      return new Promise((resolve, reject) => {
        this.client.zremrangebyrank(namespace, 0, max, (err, response) => {
          if (err) reject(err)
          resolve(response)
        })
      })
    }
  }

  async add(namespace, key) {
    if (this.client) {
      await this.removeByRange(namespace, -this.max - 1)
      return new Promise((resolve, reject) => {
        this.client.zadd(namespace, Date.now(), key, (err, response) => {
          if (err) reject(err)
          resolve(response)
        })
      })
    }
  }

  range(namespace, limit = 0) {
    if (this.client) {
      return new Promise((resolve, reject) => {
        this.client.zrevrange(namespace, 0, limit - 1, (err, response) => {
          if (err) reject(err)
          resolve(response)
        })
      })
    }
  }

  get(namespace, limit) {
    if (this.client) {
      return this.range(namespace, limit)
    }
  }
}

module.exports = LRU
<|endoftext|>


<commit_before>/**
 * @file GridGroup component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import TipProvider from '../TipProvider';
import GridItem from '../_GridItem';
import Theme from '../Theme';

import Util from '../_vendors/Util';

export default class GridGroup extends Component {

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    render() {

        const {
                children, className, style, theme, text, iconCls, rightIconCls, tip, tipPosition,
                disabled, isLoading, onMouseEnter, onMouseLeave
            } = this.props,

            listGroupClassName = (theme ? ` theme-${theme}` : '') + (className ? ' ' + className : '');

        return (
            <TipProvider className='block'
                         text={tip}
                         tipPosition={tipPosition}>

                    <GridItem className="grid-group-name"
                              text={text}
                              iconCls={iconCls}
                              rightIconCls={rightIconCls}
                              disabled={disabled}
                              isLoading={isLoading}
                              readOnly={true}/>

                        {children}

            </TipProvider>
        );

    }
};

GridGroup.propTypes = {

    /**
     * The CSS class name of the grid button.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the grid button.
     */
    style: PropTypes.object,

    /**
     * The theme of the grid button.
     */
    theme: PropTypes.oneOf(Object.keys(Theme).map(key => Theme[key])),

    /**
     * The text value of the grid button. Type can be string or number.
     */
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

    /**
     * The grid item's display text. Type can be string, number or bool.
     */
    text: PropTypes.any,

    /**
     * If true, the grid button will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * If true,the button will be have loading effect.
     */
    isLoading: PropTypes.bool,

    /**
     * Use this property to display an icon. It will display on the left.
     */
    iconCls: PropTypes.string,

    /**
     * Use this property to display an icon. It will display on the right.
     */
    rightIconCls: PropTypes.string,

    /**
     *
     */
    tip: PropTypes.string,

    /**
     *
     */
    tipPosition: PropTypes.oneOf(Util.enumerateValue(TipProvider.Position)),

    /**
     *
     */
    onMouseEnter: PropTypes.func,

    /**
     *
     */
    onMouseLeave: PropTypes.func

};

GridGroup.defaultProps = {

    className: '',
    style: null,

    theme: Theme.DEFAULT,

    value: '',
    text: '',

    disabled: false,
    isLoading: false,

    iconCls: '',
    rightIconCls: '',

    tip: '',
    tipPosition: TipProvider.Position.BOTTOM

};<commit_msg>update GridGroup: optimize code<commit_after>/**
 * @file GridGroup component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';

import TipProvider from '../TipProvider';
import GridItem from '../_GridItem';
import Theme from '../Theme';

import Util from '../_vendors/Util';

export default class GridGroup extends Component {

    constructor(props, ...restArgs) {
        super(props, ...restArgs);
    }

    render() {

        const {
                children, className, style, theme, text, iconCls, rightIconCls, tip, tipPosition,
                disabled, isLoading, onMouseEnter, onMouseLeave
            } = this.props,

            listGroupClassName = (theme ? ` theme-${theme}` : '') + (className ? ' ' + className : '');

        return (
            <TipProvider className='block'
                         text={tip}
                         tipPosition={tipPosition}>

                    <GridItem className="grid-group-name"
                              text={text}
                              iconCls={iconCls}
                              rightIconCls={rightIconCls}
                              disabled={disabled}
                              isLoading={isLoading}
                              readOnly={true}/>

                        {children}

            </TipProvider>
        );

    }
};

GridGroup.propTypes = {

    /**
     * The CSS class name of the grid button.
     */
    className: PropTypes.string,

    /**
     * Override the styles of the grid button.
     */
    style: PropTypes.object,

    /**
     * The theme of the grid button.
     */
    theme: PropTypes.oneOf(Object.keys(Theme).map(key => Theme[key])),

    /**
     * The text value of the grid button. Type can be string or number.
     */
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

    /**
     * The grid item's display text. Type can be string, number or bool.
     */
    text: PropTypes.any,

    /**
     * If true, the grid button will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * If true,the button will be have loading effect.
     */
    isLoading: PropTypes.bool,

    /**
     * Use this property to display an icon. It will display on the left.
     */
    iconCls: PropTypes.string,

    /**
     * Use this property to display an icon. It will display on the right.
     */
    rightIconCls: PropTypes.string,

    /**
     *
     */
    tip: PropTypes.string,

    /**
     *
     */
    tipPosition: PropTypes.oneOf(Util.enumerateValue(TipProvider.Position)),

    /**
     *
     */
    onMouseEnter: PropTypes.func,

    /**
     *
     */
    onMouseLeave: PropTypes.func

};

GridGroup.defaultProps = {

    className: '',
    style: null,

    theme: Theme.DEFAULT,

    value: '',
    text: '',

    disabled: false,
    isLoading: false,

    iconCls: '',
    rightIconCls: '',

    tip: '',
    tipPosition: TipProvider.Position.BOTTOM

};<|endoftext|>


<commit_before>/* @flow */

// Contains all the configuration specific to our Server.
// Please note that the DefinePlugin of webpack will inline/replace all the
// respective "process.env.*" variables below with the actual values.
// These config values then become a part of the server bundle.

import path from 'path';
import appRoot from 'app-root-path';
import * as guard from '../../shared/utils/guards';

const appRootPath = appRoot.toString();

export const SERVER_PORT = parseInt(guard.envVarExists('SERVER_PORT'), 10);

export const DISABLE_SSR = process.env.DISABLE_SSR === 'true';

export const CLIENT_BUNDLE_HTTP_PATH = guard.envVarExists('CLIENT_BUNDLE_HTTP_PATH');

export const CLIENT_BUNDLE_OUTPUT_PATH = path.resolve(
  appRootPath, guard.envVarExists('CLIENT_BUNDLE_OUTPUT_PATH')
);

export const CLIENT_BUNDLE_CACHE_MAXAGE = guard.envVarExists('CLIENT_BUNDLE_CACHE_MAXAGE');

const assetsBundleFilePath = path.resolve(
  appRootPath,
  CLIENT_BUNDLE_OUTPUT_PATH,
  guard.envVarExists('CLIENT_BUNDLE_ASSETS_FILENAME')
);
guard.fileExists(
  assetsBundleFilePath,
  `We could not find the "${assetsBundleFilePath}" file, which contains a ` +
  'list of the assets of the client bundle.  Please ensure that the client ' +
  'bundle has been built before the server bundle and that the required ' +
  'environment variables are configured (CLIENT_BUNDLE_OUTPUT_PATH & ' +
  'CLIENT_BUNDLE_ASSETS_FILENAME)'
);
export const CLIENT_BUNDLE_ASSETSJSON_FILEPATH = assetsBundleFilePath;

export const PUBLIC_DIR_PATH = path.resolve(appRootPath, './public');
<commit_msg>Fixes incorrectly placed fileExists guard.<commit_after>/* @flow */

// Contains all the configuration specific to our Server.
// Please note that the DefinePlugin of webpack will inline/replace all the
// respective "process.env.*" variables below with the actual values.
// These config values then become a part of the server bundle.

import path from 'path';
import appRoot from 'app-root-path';
import { envVarExists } from '../../shared/utils/guards';
import { fileExists } from '../utils/guards';

const appRootPath = appRoot.toString();

export const SERVER_PORT = parseInt(envVarExists('SERVER_PORT'), 10);

export const DISABLE_SSR = process.env.DISABLE_SSR === 'true';

export const CLIENT_BUNDLE_HTTP_PATH = envVarExists('CLIENT_BUNDLE_HTTP_PATH');

export const CLIENT_BUNDLE_OUTPUT_PATH = path.resolve(
  appRootPath, envVarExists('CLIENT_BUNDLE_OUTPUT_PATH')
);

export const CLIENT_BUNDLE_CACHE_MAXAGE = envVarExists('CLIENT_BUNDLE_CACHE_MAXAGE');

const assetsBundleFilePath = path.resolve(
  appRootPath,
  CLIENT_BUNDLE_OUTPUT_PATH,
  envVarExists('CLIENT_BUNDLE_ASSETS_FILENAME')
);
fileExists(
  assetsBundleFilePath,
  `We could not find the "${assetsBundleFilePath}" file, which contains a ` +
  'list of the assets of the client bundle.  Please ensure that the client ' +
  'bundle has been built before the server bundle and that the required ' +
  'environment variables are configured (CLIENT_BUNDLE_OUTPUT_PATH & ' +
  'CLIENT_BUNDLE_ASSETS_FILENAME)'
);
export const CLIENT_BUNDLE_ASSETSJSON_FILEPATH = assetsBundleFilePath;

export const PUBLIC_DIR_PATH = path.resolve(appRootPath, './public');
<|endoftext|>


<commit_before>import WebSocket from 'ws';
import querystring from 'querystring';
import Socket from './Socket';

/** @private */
const initializationCache = new Map();

/**
 * Internal base class for services and scenes.
 *
 * @todo - binary socket using:
 * https://github.com/websockets/ws#multiple-servers-sharing-a-single-https-server
 *
 * @memberof module:soundworks/server
 */
const sockets = {
  /**
   * Store sockets per room. The romm `'*'` store all current connections.
   * @private
   */
  _rooms: new Map(),

  /**
   * Initialize sockets, all sockets are added by default added to two rooms:
   * - to the room corresponding to the client `clientType`
   * - to the '*' that holds all connected sockets
   * @private
   */
  start(httpServer, config, onConnectionCallback) {
    const path = 'socket'; // should remove origin

    this.wss = new WebSocket.Server({
      server: httpServer,
      path: `/${path}`, // @note - update according to existing config files (aka cosima-apps)
    });

    this.wss.on('connection', (ws, req) => {
      const queryString = querystring.decode(req.url.split('?')[1]);
      const { clientType, key } = queryString;
      const binary = !!(parseInt(queryString.binary));

      if (binary) {
        ws.binaryType = 'arraybuffer';
      }

      if (!initializationCache.has(key)) {
        initializationCache.set(key, { ws, binary });
      } else {
        const cached = initializationCache.get(key);
        initializationCache.delete(key);

        // should be in order, but just to be sure
        const stringWs = cached.binary ? ws : cached.ws;
        const binaryWs = cached.binary ? cached.ws : ws;
        const socket = new Socket(stringWs, binaryWs, this._rooms, this, config);

        socket.addToRoom('*');
        socket.addToRoom(clientType);

        onConnectionCallback(clientType, socket);
      }
    });
  },

  /** @private */
  _broadcast(binary, roomIds, excludeSocket, channel, ...args) {
    const method = binary ? 'sendBinary' : 'send';
    let targets;

    if (typeof roomsIds === 'string' || Array.isArray(roomIds)) {
      if (typeof roomsIds === 'string') {
        roomIds = [roomIds];
      }

      targets = new Set();

      roomIds.forEach(roomId => {
        if (this._rooms.has(roomId)) {
          const room = this._rooms.get(roomId);
          room.forEach(socket => targets.add(socket));
        }
      });
    } else {
      targets = this._rooms.get('*');
    }

    targets.forEach(socket => {
      if (socket.ws.readyState === WebSocket.OPEN) {
        if (excludeSocket !== null) {
          if (socket !== excludeSocket) {
            socket[method](channel, ...args);
          }
        } else {
          socket[method](channel, ...args);
        }
      }
    });
  },

  /**
   * Add a socket to a room
   *
   * @param {Socket} socket - Socket to register in the room.
   * @param {String} roomId - Id of the room
   */
  addToRoom(socket, roomId) {
    socket.addToRoom(roomId);
  },

  /**
   * Remove a socket from a room
   *
   * @param {Socket} socket - Socket to register in the room.
   * @param {String} [roomId=null] - Id of the room
   */
  removeFromRoom(socket, roomId) {
    socket.removeFromRoom(roomId);
  },

  /**
   * Send JSON compatible messages to a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Client to send the message to
   * @param {String} channel - Channel of the message
   * @param {...*} args - Arguments of the message (as many as needed, of any type),
   *  must be compatible with JSON serialization.
   */
  send(socket, channel, ...args) {
    socket.send(channel, ...args);
  },

  /**
   * Send a string message to all client of given room(s). If no room
   * not specified, the message is sent to all clients
   *
   * @param {String|Array} roomsIds - Ids of the rooms that must receive
   *  the message. If null the message is sent to all clients
   * @param {module:soundworks/server.Socket} excludeSocket - Optionnal
   *  socket to ignore when broadcasting the message, typically the client
   *  at the origin of the message
   * @param {String} channel - Channel of the message
   * @param {...*} args - Arguments of the message (as many as needed, of any type)
   */
  broadcast(roomIds, excludeSocket, channel, ...args) {
    this._broadcast(false, roomIds, excludeSocket, channel, ...args);
  },

  /**
   * Listen JSON compatible messages from a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Socket that should listen for the message
   * @param {String} channel - Channel of the message
   * @param {Function} callback - Callback to execute when a message is received
   */
  addListener(socket, channel, callback) {
    socket.addListener(channel, callback);
  },

  /**
   * Remove a listener from JSON compatible messages from a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Socket to send the message to
   * @param {String} channel - Channel of the message
   * @param {Function} callback - Callback to cancel
   */
  removeListener(socket, channel, callback) {
    socket.removeListener(channel, callback);
  },

  /**
   * Send binary messages to a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Socket to send the message to
   * @param {String} channel - Channel of the message
   * @param {TypedArray} typedArray - Data to send
   */
  sendBinary(socket, channel, typedArray) {
    socket.sendBinary(channel, typedArray);
  },

  /**
   * Send a binary message (TypedArray) to all client of given room(s). If no room
   * not specified, the message is sent to all clients
   *
   * @param {String|Array} roomsIds - Ids of the rooms that must receive
   *  the message. If null the message is sent to all clients
   * @param {module:soundworks/server.Socket} excludeSocket - Optionnal
   *  socket to ignore when broadcasting the message, typically the client
   *  at the origin of the message
   * @param {String} channel - Channel of the message
   * @param {...*} args - Arguments of the message (as many as needed, of any type)
   */
  broadcastBinary(roomIds, excludeSocket, channel, typedArray) {
    this._broadcast(true, roomIds, excludeSocket, channel, typedArray);
  },

  /**
   * Listen binary messages from a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Client that must listen to the message
   * @param {String} channel - Channel of the message
   * @param {...*} callback - Callback to execute when a message is received
   */
  addBinaryListener(socket, channel, callback) {
    client.socket.addBinaryListener(channel, callback);
  },

  /**
   * Remove a listener from binary compatible messages form a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Client that must stop to listen for the message
   * @param {String} channel - Channel of the message
   * @param {...*} callback - Callback to cancel
   */
  removeBinaryListener(socket, channel, callback) {
    client.socket.removeBinaryListener(channel, callback);
  },
};

export default sockets;
<commit_msg>ensure the `*` room exists even when no clients connected<commit_after>import WebSocket from 'ws';
import querystring from 'querystring';
import Socket from './Socket';

/** @private */
const initializationCache = new Map();

/**
 * Internal base class for services and scenes.
 *
 * @todo - binary socket using:
 * https://github.com/websockets/ws#multiple-servers-sharing-a-single-https-server
 *
 * @memberof module:soundworks/server
 */
const sockets = {
  /**
   * Store sockets per room. The romm `'*'` store all current connections.
   * @private
   */
  _rooms: new Map(),

  /**
   * Initialize sockets, all sockets are added by default added to two rooms:
   * - to the room corresponding to the client `clientType`
   * - to the '*' that holds all connected sockets
   * @private
   */
  start(httpServer, config, onConnectionCallback) {
    const path = 'socket'; // should remove origin

    // init global room
    this._rooms.set('*', new Set());

    this.wss = new WebSocket.Server({
      server: httpServer,
      path: `/${path}`, // @note - update according to existing config files (aka cosima-apps)
    });

    this.wss.on('connection', (ws, req) => {
      const queryString = querystring.decode(req.url.split('?')[1]);
      const { clientType, key } = queryString;
      const binary = !!(parseInt(queryString.binary));

      if (binary) {
        ws.binaryType = 'arraybuffer';
      }

      if (!initializationCache.has(key)) {
        initializationCache.set(key, { ws, binary });
      } else {
        const cached = initializationCache.get(key);
        initializationCache.delete(key);

        // should be in order, but just to be sure
        const stringWs = cached.binary ? ws : cached.ws;
        const binaryWs = cached.binary ? cached.ws : ws;
        const socket = new Socket(stringWs, binaryWs, this._rooms, this, config);

        socket.addToRoom('*');
        socket.addToRoom(clientType);

        onConnectionCallback(clientType, socket);
      }
    });
  },

  /** @private */
  _broadcast(binary, roomIds, excludeSocket, channel, ...args) {
    const method = binary ? 'sendBinary' : 'send';
    let targets = new Set();

    if (typeof roomsIds === 'string' || Array.isArray(roomIds)) {
      if (typeof roomsIds === 'string') {
        roomIds = [roomIds];
      }

      roomIds.forEach(roomId => {
        if (this._rooms.has(roomId)) {
          const room = this._rooms.get(roomId);
          room.forEach(socket => targets.add(socket));
        }
      });
    } else {
      targets = this._rooms.get('*');
    }

    targets.forEach(socket => {
      if (socket.ws.readyState === WebSocket.OPEN) {
        if (excludeSocket !== null) {
          if (socket !== excludeSocket) {
            socket[method](channel, ...args);
          }
        } else {
          socket[method](channel, ...args);
        }
      }
    });
  },

  /**
   * Add a socket to a room
   *
   * @param {Socket} socket - Socket to register in the room.
   * @param {String} roomId - Id of the room
   */
  addToRoom(socket, roomId) {
    socket.addToRoom(roomId);
  },

  /**
   * Remove a socket from a room
   *
   * @param {Socket} socket - Socket to register in the room.
   * @param {String} [roomId=null] - Id of the room
   */
  removeFromRoom(socket, roomId) {
    socket.removeFromRoom(roomId);
  },

  /**
   * Send JSON compatible messages to a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Client to send the message to
   * @param {String} channel - Channel of the message
   * @param {...*} args - Arguments of the message (as many as needed, of any type),
   *  must be compatible with JSON serialization.
   */
  send(socket, channel, ...args) {
    socket.send(channel, ...args);
  },

  /**
   * Send a string message to all client of given room(s). If no room
   * not specified, the message is sent to all clients
   *
   * @param {String|Array} roomsIds - Ids of the rooms that must receive
   *  the message. If null the message is sent to all clients
   * @param {module:soundworks/server.Socket} excludeSocket - Optionnal
   *  socket to ignore when broadcasting the message, typically the client
   *  at the origin of the message
   * @param {String} channel - Channel of the message
   * @param {...*} args - Arguments of the message (as many as needed, of any type)
   */
  broadcast(roomIds, excludeSocket, channel, ...args) {
    this._broadcast(false, roomIds, excludeSocket, channel, ...args);
  },

  /**
   * Listen JSON compatible messages from a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Socket that should listen for the message
   * @param {String} channel - Channel of the message
   * @param {Function} callback - Callback to execute when a message is received
   */
  addListener(socket, channel, callback) {
    socket.addListener(channel, callback);
  },

  /**
   * Remove a listener from JSON compatible messages from a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Socket to send the message to
   * @param {String} channel - Channel of the message
   * @param {Function} callback - Callback to cancel
   */
  removeListener(socket, channel, callback) {
    socket.removeListener(channel, callback);
  },

  /**
   * Send binary messages to a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Socket to send the message to
   * @param {String} channel - Channel of the message
   * @param {TypedArray} typedArray - Data to send
   */
  sendBinary(socket, channel, typedArray) {
    socket.sendBinary(channel, typedArray);
  },

  /**
   * Send a binary message (TypedArray) to all client of given room(s). If no room
   * not specified, the message is sent to all clients
   *
   * @param {String|Array} roomsIds - Ids of the rooms that must receive
   *  the message. If null the message is sent to all clients
   * @param {module:soundworks/server.Socket} excludeSocket - Optionnal
   *  socket to ignore when broadcasting the message, typically the client
   *  at the origin of the message
   * @param {String} channel - Channel of the message
   * @param {...*} args - Arguments of the message (as many as needed, of any type)
   */
  broadcastBinary(roomIds, excludeSocket, channel, typedArray) {
    this._broadcast(true, roomIds, excludeSocket, channel, typedArray);
  },

  /**
   * Listen binary messages from a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Client that must listen to the message
   * @param {String} channel - Channel of the message
   * @param {...*} callback - Callback to execute when a message is received
   */
  addBinaryListener(socket, channel, callback) {
    client.socket.addBinaryListener(channel, callback);
  },

  /**
   * Remove a listener from binary compatible messages form a socket on a given channel
   *
   * @param {module:soundworks/server.Socket} socket - Client that must stop to listen for the message
   * @param {String} channel - Channel of the message
   * @param {...*} callback - Callback to cancel
   */
  removeBinaryListener(socket, channel, callback) {
    client.socket.removeBinaryListener(channel, callback);
  },
};

export default sockets;
<|endoftext|>


<commit_before>/*
 * Copyright 2012 VirtuOz Inc.  All rights reserved.
 */

/**
 *
 * @author ccauchois
 * @created 2012-10-24
 */

require('jsclass');
JS.require('JS.Class');
var EventEmitter = require('events').EventEmitter;
var Io = require('socket.io');
var Amqp = require('amqp');
var QUEUEFLAG = require('./const').QUEUEFLAG;

var SmartRouter = new JS.Class(EventEmitter, {
  initialize: function () {
    EventEmitter.call(this);
    this.io = {};
    this.amqp = {};
    this.config = {};
  },

  /**
   * start: starts the SmartRouter
   *
   */
  start: function (config) {
    console.log('starting SmartRouter...');
    this.config = config;
    this.io = Io.listen(config.port);
    this.amqp = Amqp.createConnection(config.amqp);
    this.amqp.on('error', function (err) {
      console.log('error connecting to RabbitMQ: ' + err);
    });

    this.registerEndpoints();

    this.emit('started');
  },

  /**
   * registerEndpoints: register bindings for the endpoints.
   *
   */
  registerEndpoints: function () {
    var self = this;
    self.config.endpoints.forEach(function (endpoint) {
      console.log('registering endpoint ' + endpoint.name);
      if (endpoint.ids.length === 0) {
        var socketendpoint = self.io.of('/' + endpoint.name);
        self.registerRoutes(endpoint.name, '', endpoint.queue, socketendpoint);
      } else {
        endpoint.ids.forEach(function (id) {
          var socketendpoint = self.io.of('/' + endpoint.name + '/' + id);
          self.registerRoutes(endpoint.name, id, endpoint.queue, socketendpoint);
        });
      }
    });
  },

  /**
   * registerRoutes
   */
  registerRoutes: function (endpointname, endpointid, queuebehaviour, socketep) {
    var self = this;
    socketep.on('connection', function (socket) {
      socket.emit('whoareyou');
      socket.on('iam', function (actorid) {
        socket.set('actorid', actorid, function () {
          console.log('hello actor ' + actorid);
          socket.emit('hello');
        });
        var exchangename = endpointname + '/' + endpointid;
        self.amqp.exchange(exchangename, { type: 'direct', durable: true, autoDelete: false });
        if (!queuebehaviour|| (queuebehaviour & QUEUEFLAG.actor > 0)) {
          self.amqp.queue(actorid, { autoDelete: false }, function (q) {
            q.bind(exchangename, actorid);
            q.subscribe(function (message, headers, deliveryInfo) {
              console.log('emiting ' + message.type + ' to ' + actorid);
              socket.emit(message.type, message.message);
            });
          });
        }
        if (queuebehaviour & QUEUEFLAG.endpoint > 0) {
          self.amqp.queue(exchangename, { autoDelete: false }, function (q) {
            q.bind(exchangename, exchangename);
            q.subscribe(function (message, headers, deliveryInfo) {
              console.log('emiting ' + message.type + ' to ' + exchangename);
              socket.emit(message.type, message.message);
            });
          });
        }
      });
      self.config.routes.forEach( function (route) {
        if (route.endpoint === '*' || route.endpoint === endpointname) {
          console.log('registering ' + route.messagetype + ' for ' + route.endpoint);
          socket.on(route.messagetype, function (message) {
            self.check(socket, route.messagetype, message, function (err, checked) {
              if (err) {
                console.log(err);
              }
              if (checked) {
                console.log('routing ' + route.messagetype + ' from  ' + endpointname);
                route.action(message, socket, self);
              }
            });
          });
        }
      });
    });
  },

  /**
   * check: performs some checks on the message received.
   *
   */
  check: function (socket, type, message, callback) {
    socket.get('actorid', function (err, id) {
      if (!id) {
        var data = {};
        data.type = type;
        data.message = message;
        socket.emit('whoareyou', data);
        return callback(err, false);
      }
      // here we can add additional checks.
      return callback(err, !!id);
    });
  },

  /**
   * publish: hides the complexity of publishing to the different queues
   *
   */
  publish: function (endpoint, destactorid, type, message) {
    var qmsg = {};
    qmsg.type = type;
    qmsg.message = message;
    this.amqp.exchange(endpoint, { passive: true}).publish(destactorid, qmsg, { contentType: 'application/json' });
  }
});

exports.instance = new SmartRouter();
<commit_msg>fixing unsubscribe<commit_after>/*
 * Copyright 2012 VirtuOz Inc.  All rights reserved.
 */

/**
 *
 * @author ccauchois
 * @created 2012-10-24
 */

require('jsclass');
JS.require('JS.Class');
var EventEmitter = require('events').EventEmitter;
var Io = require('socket.io');
var Amqp = require('amqp');
var QUEUEFLAG = require('./const').QUEUEFLAG;

var SmartRouter = new JS.Class(EventEmitter, {
  initialize: function () {
    EventEmitter.call(this);
    this.io = {};
    this.amqp = {};
    this.config = {};
  },

  /**
   * start: starts the SmartRouter
   *
   */
  start: function (config) {
    console.log('starting SmartRouter...');
    this.config = config;
    this.io = Io.listen(config.port);
    this.amqp = Amqp.createConnection(config.amqp);
    this.amqp.on('error', function (err) {
      console.log('error connecting to RabbitMQ: ' + err);
    });

    this.registerEndpoints();

    this.emit('started');
  },

  /**
   * registerEndpoints: register bindings for the endpoints.
   *
   */
  registerEndpoints: function () {
    var self = this;
    self.config.endpoints.forEach(function (endpoint) {
      console.log('registering endpoint ' + endpoint.name);
      if (endpoint.ids.length === 0) {
        var socketendpoint = self.io.of('/' + endpoint.name);
        self.registerRoutes(endpoint.name, '', endpoint.queue, socketendpoint);
      } else {
        endpoint.ids.forEach(function (id) {
          var socketendpoint = self.io.of('/' + endpoint.name + '/' + id);
          self.registerRoutes(endpoint.name, id, endpoint.queue, socketendpoint);
        });
      }
    });
  },

  /**
   * registerRoutes
   */
  registerRoutes: function (endpointname, endpointid, queuebehaviour, socketep) {
    var self = this;
    socketep.on('connection', function (socket) {
      socket.emit('whoareyou');
      socket.on('iam', function (actorid) {
        socket.set('actorid', actorid, function () {
          console.log('hello actor ' + actorid);
          socket.emit('hello');
        });
        var exchangename = endpointname + '/' + endpointid;
        self.amqp.exchange(exchangename, { type: 'direct', durable: true, autoDelete: false });
        if (!queuebehaviour|| (queuebehaviour & QUEUEFLAG.actor > 0)) {
          self.queueSubscribe(exchangename, actorid, socket);
        }
        if (queuebehaviour & QUEUEFLAG.endpoint > 0) {
          self.queueSubscribe(exchangename, exchangename, socket);
        }
      });
      socket.on('disconnect', function () {
        self.queuesUnsubscribe(socket);
      });
      self.config.routes.forEach( function (route) {
        if (route.endpoint === '*' || route.endpoint === endpointname) {
          console.log('registering ' + route.messagetype + ' for ' + route.endpoint);
          socket.on(route.messagetype, function (message) {
            self.check(socket, route.messagetype, message, function (err, checked) {
              if (err) {
                console.log(err);
              }
              if (checked) {
                console.log('routing ' + route.messagetype + ' from  ' + endpointname);
                route.action(message, socket, self);
              }
            });
          });
        }
      });
    });
  },
  /**
   * queueSubscribe: subscribe to the right queue
   *
   */
  queueSubscribe: function (exchangename, queuename, socket) {
    var self = this;
    self.amqp.queue(queuename, { autoDelete: false }, function (q) {
      q.bind(exchangename, queuename);
      q.subscribe(function (message, headers, deliveryInfo) {
        console.log('emiting ' + message.type + ' to ' + queuename + ' on ' + socket.id);
        socket.emit(message.type, message.message);
      })
      .addCallback(function (ok) {
        var data = { queue: q, ctag: ok.consumerTag };
        socket.get('queueData', function (err, queueData) {
          queueData = queueData || [];
          queueData.push(data);
          socket.set('queueData',queueData);
        });
      });
    });
  },

  /**
   * queueUnsubscribe: unsubscribes from the queues
   *
   */
  queuesUnsubscribe: function (socket) {
    var self = this;
    socket.get('queueData', function (err, queueData) {
      if (queueData) {
        queueData.forEach(function (item) {
          item.queue.unsubscribe(item.ctag).addCallback(function () {
            console.log('unsubscribed ' + item.name + ' with ' + item.ctag + ' for ' + socket.id);
          });
        });
      }
    });
  },

  /**
   * check: performs some checks on the message received.
   *
   */
  check: function (socket, type, message, callback) {
    socket.get('actorid', function (err, id) {
      if (!id) {
        var data = {};
        data.type = type;
        data.message = message;
        socket.emit('whoareyou', data);
        return callback(err, false);
      }
      // here we can add additional checks.
      return callback(err, !!id);
    });
  },

  /**
   * publish: hides the complexity of publishing to the different queues
   *
   */
  publish: function (endpoint, destactorid, type, message) {
    var qmsg = {};
    qmsg.type = type;
    qmsg.message = message;
    this.amqp.exchange(endpoint, { passive: true}).publish(destactorid, qmsg, { contentType: 'application/json' });
  }
});

exports.instance = new SmartRouter();
<|endoftext|>


<commit_before>const service = require('feathers-mongoose');
const { Forbidden, NotFound } = require('@feathersjs/errors');
const logger = require('winston');
const { newsModel, newsHistoryModel } = require('./model');
const hooks = require('./hooks');
const newsModelHooks = require('./hooks/model');
const { flatten, paginate, sort } = require('../../utils/array');

class NewsService {
	setup(app) {
		this.app = app;
	}

	/**
	 * Checks scoped permission for a user.
	 * @param {BsonId|String} userId
	 * @param {String} permission
	 * @param {BsonId|String} targetId (optional) news target (scope) id. Only valid with targetModel.
	 * @param {String} targetModel (optional) news target (scope) model. Only valid with target.
	 * @returns {Promise<Boolean>} Promise that resolves to true/false
	 * @example
	 * await hasPermission(user._id, 'NEWS_VIEW') => true
	 * await hasPermission(user._id, 'NEWS_CREATE', team._id, 'teams') => false
	 * @memberof NewsService
	 */
	async hasPermission(userId, permission, targetId, targetModel) {
		if (targetId && targetModel) {
			const scope = this.app.service(`${targetModel}/:scopeId/userPermissions/`);
			const params = { route: { scopeId: targetId.toString() } };
			const scopePermissions = await scope.get(userId, params);
			return scopePermissions.includes(permission);
		}
		const user = await this.app.service('users').get(userId);
		return user.permissions.includes(permission);
	}

	/**
	 * Throw an error if the user is not allowed to perform the given operation
	 * @param {News} news news item (required: {schoolId}, optional: {target, targetModel})
	 * @param {Object} { userId, schoolId }
	 * @param {String} permission permision to check
	 * @returns undefined
	 * @example
	 * await authorize(news, params.account, 'NEWS_VIEW') => undefined
	 * await authorize(news, params.account, 'NEWS_CREATE') => (throws Forbidden)
	 * @memberof NewsService
	 */
	async authorize(news = {}, { userId, schoolId } = {}, permission) {
		const authorized = await this.hasPermission(userId, permission, news.target, news.targetModel);
		const sameSchool = news.schoolId.toString() === schoolId.toString();
		if (!authorized || !sameSchool) {
			throw new Forbidden('Not authorized.');
		}
	}

	checkExistence(news, query) {
		if (!news) {
			logger.error(`Cannot find news item with query "${query}" => "${news}"`);
			throw new NotFound('News item does not exist.');
		}
	}

	/**
	 * Returns all school news the user is allowed to see.
	 * @param {Object} { userId, schoolId } -- The user's Id and schoolId
	 * @returns Array<News Document>
	 * @memberof NewsService
	 */
	async findSchoolNews({ userId, schoolId }) {
		if (!this.hasPermission(userId, 'NEWS_VIEW')) {
			throw new Forbidden('Mising permissions to view school news.');
		}
		const query = { schoolId, target: { $exists: false } };
		return this.app.service('newsModel').find({ query, paginate: false });
	}

	/**
	 * Returns scoped news the user is allowed to see
	 *
	 * @param {BsonId|String} userId the user's Id
	 * @param {BsonId|String} target (optional) Id of the news target (course, team, etc.)
	 * @returns Array<News Document>
	 * @memberof NewsService
	 */
	async findScopedNews(userId, target) {
		const scopes = await newsModel.distinct('targetModel');
		const ops = scopes.map(async (scope) => {
			// For each possible target model, find all targets the user has NEWS_VIEW permissions in.
			const scopeListService = this.app.service(`/users/:scopeId/${scope}`);
			if (scopeListService === undefined) {
				throw new Error(`Missing ScopeListService for scope "${scope}".`);
			}
			let scopeItems = await scopeListService.find({
				route: { scopeId: userId.toString() },
				query: { permissions: ['NEWS_VIEW'] },
			});
			if (target) {
				// if a target id is given, only return news from this target
				scopeItems = scopeItems.filter(i => i._id.toString() === target.toString());
			}
			return Promise.all(scopeItems.map(async (item) => {
				const query = {
					targetModel: scope,
					target: item._id,
					$populate: target,
				};
				const news = await this.app.service('newsModel').find({ query, paginate: false });
				// Manually populate the target (current API requires this):
				return Promise.all(news.map(async n => ({
					...n,
					target: await this.app.service(scope).get(n.target),
				})));
			}));
		});
		const results = await Promise.all(ops);
		return flatten(results);
	}

	/**
	 * Create a copy of the original news if it is edited
	 * @param {News} oldItem
	 * @returns {Promise<NewsHistory>} the created news history document
	 * @memberof NewsService
	 * @static
	 */
	static createHistoryEntry(oldItem) {
		const historyEntry = {
			title: oldItem.title,
			content: oldItem.content,
			displayAt: oldItem.displayAt,
			creatorId: oldItem.updaterId ? oldItem.updaterId : oldItem.creatorId,
			parentId: oldItem._id,
		};
		return newsHistoryModel.create(historyEntry);
	}

	/**
	 * GET /news/{id}
	 * Returns the news item specified by id
	 * @param {BsonId|String} id
	 * @param {Object} params
	 * @returns one news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async get(id, params) {
		const news = await this.app.service('newsModel').get(id);
		this.checkExistence(news, id);
		await this.authorize(news, params.account, 'NEWS_VIEW');
		return news;
	}

	/**
	 * GET /news/
	 * Returns all news the user can see.
	 * @param {Object} params
	 * @returns paginated array of news items
	 * @memberof NewsService
	 */
	async find(params) {
		let news = [];
		const scoped = params.query && params.query.target;
		if (scoped) {
			news = news.concat(await this.findScopedNews(params.account.userId, params.query.target));
		} else {
			news = news.concat(await this.findSchoolNews(params.account));
			news = news.concat(await this.findScopedNews(params.account.userId));
		}
		if (params.query) {
			news = sort(news, params.query.$sort);
		}
		// paginate by default, but let $paginate=false through
		news = paginate(news, { $paginate: true, ...params.query });
		return Promise.resolve(news);
	}

	/**
	 * POST /news/
	 * Creates a news item
	 * @param {Object} data @see NewsModel
	 * @param {Object} params
	 * @returns {News} the created news object
	 * @throws {Forbidden} if not authorized
	 * @memberof NewsService
	 */
	async create(data, params) {
		await this.authorize(data, params.account, 'NEWS_CREATE');
		return this.app.service('newsModel').create(data);
	}

	/**
	 * DELETE /news/{id}
	 * Deletes a single news item.
	 * @param {BsonId|String} id The news item's Id
	 * @param {Object} params Note that params.query won't work here
	 * @returns {News} the deleted news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async remove(id, params) {
		const news = await this.app.service('newsModel').get(id);
		this.checkExistence(news, id);
		await this.authorize(news, params.account, 'NEWS_CREATE');
		await this.app.service('newsModel').remove(id);
		return news;
	}

	/**
	 * PUT /news/{id}
	 * Replaces a single news item
	 * @param {BsonId|String} id The news item's Id
	 * @param {News} data updated news item
	 * @param {Object} params Feathers request params (note that using params.query won't work here)
	 * @returns {News} updated news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async update(id, data, params) {
		const news = await this.app.service('newsModel').get(id);
		this.checkExistence(news, id);
		await this.authorize(news, params.account, 'NEWS_EDIT');
		const updatedNews = await this.app.service('newsModel').update(id, data);
		await NewsService.createHistoryEntry(news);
		return updatedNews;
	}

	/**
	 * PATCH /news/{id}
	 * Patches a single news item
	 * @param {BsonId|String} id The news item's Id
	 * @param {Object} data object containing updated news item attributes
	 * @param {Object} params Feathers request params (note that using params.query won't work here)
	 * @returns {News} patched news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async patch(id, data, params) {
		const news = await this.app.service('newsModel').get(id);
		this.checkExistence(news, id);
		await this.authorize(news, params.account, 'NEWS_EDIT');
		const patchedNews = await this.app.service('newsModel').patch(id, data);
		await NewsService.createHistoryEntry(news);
		return patchedNews;
	}
}

module.exports = function news() {
	const app = this;

	// use /news to access a user's news
	app.use('/news', new NewsService());
	app.service('news').hooks(hooks);

	// use /newsModel to directly access the model from other services
	// (external requests are blocked)
	app.use('/newsModel', service({
		Model: newsModel,
		lean: true,
		paginate: {
			default: 25,
		},
	}));
	app.service('/newsModel').hooks(newsModelHooks);
};
<commit_msg>add permissions to news get route<commit_after>const service = require('feathers-mongoose');
const { Forbidden, NotFound } = require('@feathersjs/errors');
const logger = require('winston');
const { newsModel, newsHistoryModel } = require('./model');
const hooks = require('./hooks');
const newsModelHooks = require('./hooks/model');
const { flatten, paginate, sort } = require('../../utils/array');

class NewsService {
	setup(app) {
		this.app = app;
	}

	/**
	 * Checks scoped permission for a user.
	 * @param {BsonId|String} userId
	 * @param {String} permission
	 * @param {BsonId|String} targetId (optional) news target (scope) id. Only valid with targetModel.
	 * @param {String} targetModel (optional) news target (scope) model. Only valid with target.
	 * @returns {Promise<Boolean>} Promise that resolves to true/false
	 * @example
	 * await hasPermission(user._id, 'NEWS_VIEW') => true
	 * await hasPermission(user._id, 'NEWS_CREATE', team._id, 'teams') => false
	 * @memberof NewsService
	 */
	async hasPermission(userId, permission, targetId, targetModel) {
		if (targetId && targetModel) {
			const scope = this.app.service(`${targetModel}/:scopeId/userPermissions/`);
			const params = { route: { scopeId: targetId.toString() } };
			const scopePermissions = await scope.get(userId, params);
			return scopePermissions.includes(permission);
		}
		const user = await this.app.service('users').get(userId);
		return user.permissions.includes(permission);
	}

	/**
	 * Throw an error if the user is not allowed to perform the given operation
	 * @param {News} news news item (required: {schoolId}, optional: {target, targetModel})
	 * @param {Object} { userId, schoolId }
	 * @param {String} permission permision to check
	 * @returns undefined
	 * @example
	 * await authorize(news, params.account, 'NEWS_VIEW') => undefined
	 * await authorize(news, params.account, 'NEWS_CREATE') => (throws Forbidden)
	 * @memberof NewsService
	 */
	async authorize(news = {}, { userId, schoolId } = {}, permission) {
		const authorized = await this.hasPermission(userId, permission, news.target, news.targetModel);
		const sameSchool = news.schoolId.toString() === schoolId.toString();
		if (!authorized || !sameSchool) {
			throw new Forbidden('Not authorized.');
		}
	}

	async addPermissions(news, { userId, schoolId } = {}, permissions) {
		const retValue = [];
		for (const key of Object.keys(permissions)) {
			try {
				await this.authorize(news, { userId, schoolId }, key);
				retValue.push(permissions[key]);
			} catch (err) {
				// ignore
			}
		}
		news.permissions = retValue;
	}

	checkExistence(news, query) {
		if (!news) {
			logger.error(`Cannot find news item with query "${query}" => "${news}"`);
			throw new NotFound('News item does not exist.');
		}
	}

	/**
	 * Returns all school news the user is allowed to see.
	 * @param {Object} { userId, schoolId } -- The user's Id and schoolId
	 * @returns Array<News Document>
	 * @memberof NewsService
	 */
	async findSchoolNews({ userId, schoolId }) {
		if (!this.hasPermission(userId, 'NEWS_VIEW')) {
			throw new Forbidden('Mising permissions to view school news.');
		}
		const query = { schoolId, target: { $exists: false } };
		return this.app.service('newsModel').find({ query, paginate: false });
	}

	/**
	 * Returns scoped news the user is allowed to see
	 *
	 * @param {BsonId|String} userId the user's Id
	 * @param {BsonId|String} target (optional) Id of the news target (course, team, etc.)
	 * @returns Array<News Document>
	 * @memberof NewsService
	 */
	async findScopedNews(userId, target) {
		const scopes = await newsModel.distinct('targetModel');
		const ops = scopes.map(async (scope) => {
			// For each possible target model, find all targets the user has NEWS_VIEW permissions in.
			const scopeListService = this.app.service(`/users/:scopeId/${scope}`);
			if (scopeListService === undefined) {
				throw new Error(`Missing ScopeListService for scope "${scope}".`);
			}
			let scopeItems = await scopeListService.find({
				route: { scopeId: userId.toString() },
				query: { permissions: ['NEWS_VIEW'] },
			});
			if (target) {
				// if a target id is given, only return news from this target
				scopeItems = scopeItems.filter(i => i._id.toString() === target.toString());
			}
			return Promise.all(scopeItems.map(async (item) => {
				const query = {
					targetModel: scope,
					target: item._id,
					$populate: target,
				};
				const news = await this.app.service('newsModel').find({ query, paginate: false });
				// Manually populate the target (current API requires this):
				return Promise.all(news.map(async n => ({
					...n,
					target: await this.app.service(scope).get(n.target),
				})));
			}));
		});
		const results = await Promise.all(ops);
		return flatten(results);
	}

	/**
	 * Create a copy of the original news if it is edited
	 * @param {News} oldItem
	 * @returns {Promise<NewsHistory>} the created news history document
	 * @memberof NewsService
	 * @static
	 */
	static createHistoryEntry(oldItem) {
		const historyEntry = {
			title: oldItem.title,
			content: oldItem.content,
			displayAt: oldItem.displayAt,
			creatorId: oldItem.updaterId ? oldItem.updaterId : oldItem.creatorId,
			parentId: oldItem._id,
		};
		return newsHistoryModel.create(historyEntry);
	}

	/**
	 * GET /news/{id}
	 * Returns the news item specified by id
	 * @param {BsonId|String} id
	 * @param {Object} params
	 * @returns one news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async get(id, params) {
		const news = await this.app.service('newsModel').get(id);
		this.checkExistence(news, id);
		await this.authorize(news, params.account, 'NEWS_VIEW');
		await this.addPermissions(news, params.account, {
			NEWS_VIEW: 'view',
			NEWS_EDIT: 'edit',
			NEWS_DELETE: 'delete',
		});
		return news;
	}

	/**
	 * GET /news/
	 * Returns all news the user can see.
	 * @param {Object} params
	 * @returns paginated array of news items
	 * @memberof NewsService
	 */
	async find(params) {
		let news = [];
		const scoped = params.query && params.query.target;
		if (scoped) {
			news = news.concat(await this.findScopedNews(params.account.userId, params.query.target));
		} else {
			news = news.concat(await this.findSchoolNews(params.account));
			news = news.concat(await this.findScopedNews(params.account.userId));
		}
		if (params.query) {
			news = sort(news, params.query.$sort);
		}
		// paginate by default, but let $paginate=false through
		news = paginate(news, { $paginate: true, ...params.query });
		return Promise.resolve(news);
	}

	/**
	 * POST /news/
	 * Creates a news item
	 * @param {Object} data @see NewsModel
	 * @param {Object} params
	 * @returns {News} the created news object
	 * @throws {Forbidden} if not authorized
	 * @memberof NewsService
	 */
	async create(data, params) {
		await this.authorize(data, params.account, 'NEWS_CREATE');
		return this.app.service('newsModel').create(data);
	}

	/**
	 * DELETE /news/{id}
	 * Deletes a single news item.
	 * @param {BsonId|String} id The news item's Id
	 * @param {Object} params Note that params.query won't work here
	 * @returns {News} the deleted news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async remove(id, params) {
		const news = await this.app.service('newsModel').get(id);
		this.checkExistence(news, id);
		await this.authorize(news, params.account, 'NEWS_CREATE');
		await this.app.service('newsModel').remove(id);
		return news;
	}

	/**
	 * PUT /news/{id}
	 * Replaces a single news item
	 * @param {BsonId|String} id The news item's Id
	 * @param {News} data updated news item
	 * @param {Object} params Feathers request params (note that using params.query won't work here)
	 * @returns {News} updated news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async update(id, data, params) {
		const news = await this.app.service('newsModel').get(id);
		this.checkExistence(news, id);
		await this.authorize(news, params.account, 'NEWS_EDIT');
		const updatedNews = await this.app.service('newsModel').update(id, data);
		await NewsService.createHistoryEntry(news);
		return updatedNews;
	}

	/**
	 * PATCH /news/{id}
	 * Patches a single news item
	 * @param {BsonId|String} id The news item's Id
	 * @param {Object} data object containing updated news item attributes
	 * @param {Object} params Feathers request params (note that using params.query won't work here)
	 * @returns {News} patched news item
	 * @throws {Forbidden} if not authorized
	 * @throws {NotFound} if the id does not belong to a news object
	 * @memberof NewsService
	 */
	async patch(id, data, params) {
		const news = await this.app.service('newsModel').get(id);
		this.checkExistence(news, id);
		await this.authorize(news, params.account, 'NEWS_EDIT');
		const patchedNews = await this.app.service('newsModel').patch(id, data);
		await NewsService.createHistoryEntry(news);
		return patchedNews;
	}
}

module.exports = function news() {
	const app = this;

	// use /news to access a user's news
	app.use('/news', new NewsService());
	app.service('news').hooks(hooks);

	// use /newsModel to directly access the model from other services
	// (external requests are blocked)
	app.use('/newsModel', service({
		Model: newsModel,
		lean: true,
		paginate: {
			default: 25,
		},
	}));
	app.service('/newsModel').hooks(newsModelHooks);
};
<|endoftext|>


<commit_before>goog.provide('olcs.AbstractSynchronizer');

goog.require('ol.Observable');
goog.require('ol.layer.Group');
goog.require('ol.layer.Layer');

/**
 * @param {!ol.Map} map
 * @param {!Cesium.Scene} scene
 * @constructor
 * @template T
 * @api
 */
olcs.AbstractSynchronizer = function(map, scene) {
  /**
   * @type {!ol.Map}
   * @protected
   */
  this.map = map;

  /**
   * @type {ol.View}
   * @protected
   */
  this.view = map.getView();

  /**
   * @type {!Cesium.Scene}
   * @protected
   */
  this.scene = scene;

  /**
   * @type {ol.Collection.<ol.layer.Base>}
   * @protected
   */
  this.olLayers = map.getLayerGroup().getLayers();

  /**
   * @type {ol.layer.Group}
   */
  this.mapLayerGroup = map.getLayerGroup();

  /**
   * Map of ol3 layer ids (from goog.getUid) to the Cesium ImageryLayers.
   * null value means, that we are unable to create equivalent layer.
   * @type {Object.<number, ?T>}
   * @protected
   */
  this.layerMap = {};

  /**
   * Map of listen keys for ol3 layer groups ids (from goog.getUid).
   * @type {!Object.<number, !Array.<goog.events.Key>>}
   * @private
   */
  this.olGroupListenKeys_ = {};
};

/**
 * Remove all and perform complete synchronization of the layers.
 * @api
 */
olcs.AbstractSynchronizer.prototype.synchronize = function() {
  this.destroyAll();
  this.synchronize_();
};

/**
 * @param {ol.layer.Base} layer
 * @param {Array.<ol.layer.Layer>} foundLayers
 * @param {Array.<ol.layer.Group>} foundGroups
 * @private
 */
olcs.AbstractSynchronizer.flattenLayers_ =
    function(layer, foundLayers, foundGroups) {
  if (layer instanceof ol.layer.Group) {
    foundGroups.push(layer);
    var sublayers = layer.getLayers();
    if (goog.isDef(sublayers)) {
      sublayers.forEach(function(el) {
        olcs.AbstractSynchronizer.flattenLayers_(el, foundLayers, foundGroups);
      });
    }
  } else {
    foundLayers.push(layer);
  }
};

/**
 * Perform complete synchronization of the layers.
 * @private
 */
olcs.AbstractSynchronizer.prototype.synchronize_ = function() {
  var layers = [];
  var groups = [];
  olcs.AbstractSynchronizer.flattenLayers_(this.mapLayerGroup, layers, groups);

  layers.forEach(function(el) {
    this.synchronizeSingle(el);
  }, this);

  groups.forEach(function(el) {
    this.listenForGroupChanges_(el);
  }, this);
};

/**
 * Destroy single layer.
 * @param {ol.layer.Layer} layer
 * @private
 */
olcs.AbstractSynchronizer.prototype.removeAndDestroySingleLayer_ =
    function(layer) {
  var uid = goog.getUid(layer);
  var counterpart = this.layerMap[uid];
  if (!!counterpart) {
    this.removeSingleCesiumObject(counterpart, false);
    this.destroyCesiumObject(counterpart);
  }
  delete this.layerMap[uid];
};

/**
 * Destroy single group.
 * @param {ol.layer.Group} group
 * @private
 */
olcs.AbstractSynchronizer.prototype.unlistenSingleGroup_ =
    function(group) {
  if (group === this.mapLayerGroup) {
    return;
  }
  var uid = goog.getUid(group);
  var keys = this.olGroupListenKeys_[uid];
  keys.forEach(function(key) {
    ol.Observable.unByKey(key);
  });
  delete this.olGroupListenKeys_[uid];
};

/**
 * Remove layer.
 * @param {ol.layer.Base} removeRoot
 * @private
 */
olcs.AbstractSynchronizer.prototype.removeLayer_ = function(removeRoot) {
  if (!removeRoot) {
    return;
  }
  var layers = [];
  var groups = [];
  olcs.AbstractSynchronizer.flattenLayers_(removeRoot, layers, groups);

  layers.forEach(function(el) {
    this.removeAndDestroySingleLayer_(el);
  }, this);

  groups.forEach(function(el) {
    this.unlistenSingleGroup_(el);
  }, this);
};

/**
 * Synchronizes single layer.
 * @param {ol.layer.Group} group
 * @private
 */
olcs.AbstractSynchronizer.prototype.listenForGroupChanges_ = function(group) {
  var uuid = goog.getUid(group);

  if (!goog.isDef(this.olGroupListenKeys_[uuid])) {
    var listenKeyArray = [];
    this.olGroupListenKeys_[uuid] = listenKeyArray;

    // only the keys that need to be relistened when collection changes
    var contentKeys = [];
    var listenAddRemove = goog.bind(function() {
      var collection = group.getLayers();
      if (goog.isDef(collection)) {
        var handleContentChange_ = goog.bind(function(e) {
          // TODO: should remove the subtree
          // should synchronize the subtree
          this.synchronize_();
        }, this);
        contentKeys = [
          collection.on('add', handleContentChange_),
          collection.on('remove', function(event) {
            this.removeAndDestroySingleLayer_(event.element);
          }, this)
        ];
        listenKeyArray.push.apply(listenKeyArray, contentKeys);
      }
    }, this);
    listenAddRemove();

    listenKeyArray.push(group.on('change:layers', function(e) {
      goog.array.forEach(contentKeys, function(el) {
        goog.array.remove(listenKeyArray, el);
        ol.Observable.unByKey(el);
      });
      listenAddRemove();
    }));
  }

};

/**
 * Synchronizes single layer.
 * @param {ol.layer.Base} olLayer
 * @protected
 */
olcs.AbstractSynchronizer.prototype.synchronizeSingle = function(olLayer) {
  if (goog.isNull(olLayer)) {
    return;
  }
  var olLayerId = goog.getUid(olLayer);

  // handle layer groups
  goog.asserts.assert(!(olLayer instanceof ol.layer.Group));
  goog.asserts.assert(olLayer instanceof ol.layer.Layer);

  var cesiumObject = this.layerMap[olLayerId];

  // no mapping -> create new layer and set up synchronization
  if (!goog.isDef(cesiumObject)) {
    cesiumObject = this.createSingleCounterpart(olLayer);
    this.layerMap[olLayerId] = cesiumObject;
  }

  // add Cesium layers
  if (goog.isDefAndNotNull(cesiumObject)) {
    this.addCesiumObject(cesiumObject);
  }
};

/**
 * Destroys all the created Cesium objects.
 * @protected
 */
olcs.AbstractSynchronizer.prototype.destroyAll = function() {
  this.removeAllCesiumObjects(true); // destroy
  goog.object.forEach(this.olGroupListenKeys, ol.Observable.unByKey);
  this.olGroupListenKeys = {};
  this.layerMap = {};
};

/**
 * Adds a single Cesium object to the collection.
 * @param {!T} object
 * @protected
 */
olcs.AbstractSynchronizer.prototype.addCesiumObject = goog.abstractMethod;

/**
 * @param {!T} object
 * @protected
 */
olcs.AbstractSynchronizer.prototype.destroyCesiumObject = goog.abstractMethod;

/**
 * Remove single Cesium object from the collection.
 * @param {!T} object
 * @param {boolean} destroy
 * @protected
 */
olcs.AbstractSynchronizer.prototype.removeSingleCesiumObject =
    goog.abstractMethod;

/**
 * Remove all Cesium objects from the collection.
 * @param {boolean} destroy
 * @protected
 */
olcs.AbstractSynchronizer.prototype.removeAllCesiumObjects =
    goog.abstractMethod;

/**
 * @param {!ol.layer.Layer} olLayer
 * @return {T}
 * @protected
 */
olcs.AbstractSynchronizer.prototype.createSingleCounterpart =
    goog.abstractMethod;
<commit_msg>Handle group removal<commit_after>goog.provide('olcs.AbstractSynchronizer');

goog.require('ol.Observable');
goog.require('ol.layer.Group');
goog.require('ol.layer.Layer');

/**
 * @param {!ol.Map} map
 * @param {!Cesium.Scene} scene
 * @constructor
 * @template T
 * @api
 */
olcs.AbstractSynchronizer = function(map, scene) {
  /**
   * @type {!ol.Map}
   * @protected
   */
  this.map = map;

  /**
   * @type {ol.View}
   * @protected
   */
  this.view = map.getView();

  /**
   * @type {!Cesium.Scene}
   * @protected
   */
  this.scene = scene;

  /**
   * @type {ol.Collection.<ol.layer.Base>}
   * @protected
   */
  this.olLayers = map.getLayerGroup().getLayers();

  /**
   * @type {ol.layer.Group}
   */
  this.mapLayerGroup = map.getLayerGroup();

  /**
   * Map of ol3 layer ids (from goog.getUid) to the Cesium ImageryLayers.
   * null value means, that we are unable to create equivalent layer.
   * @type {Object.<number, ?T>}
   * @protected
   */
  this.layerMap = {};

  /**
   * Map of listen keys for ol3 layer groups ids (from goog.getUid).
   * @type {!Object.<number, !Array.<goog.events.Key>>}
   * @private
   */
  this.olGroupListenKeys_ = {};
};

/**
 * Remove all and perform complete synchronization of the layers.
 * @api
 */
olcs.AbstractSynchronizer.prototype.synchronize = function() {
  this.destroyAll();
  this.synchronize_();
};

/**
 * @param {ol.layer.Base} layer
 * @param {Array.<ol.layer.Layer>} foundLayers
 * @param {Array.<ol.layer.Group>} foundGroups
 * @private
 */
olcs.AbstractSynchronizer.flattenLayers_ =
    function(layer, foundLayers, foundGroups) {
  if (layer instanceof ol.layer.Group) {
    foundGroups.push(layer);
    var sublayers = layer.getLayers();
    if (goog.isDef(sublayers)) {
      sublayers.forEach(function(el) {
        olcs.AbstractSynchronizer.flattenLayers_(el, foundLayers, foundGroups);
      });
    }
  } else {
    foundLayers.push(layer);
  }
};

/**
 * Perform complete synchronization of the layers.
 * @private
 */
olcs.AbstractSynchronizer.prototype.synchronize_ = function() {
  var layers = [];
  var groups = [];
  olcs.AbstractSynchronizer.flattenLayers_(this.mapLayerGroup, layers, groups);

  layers.forEach(function(el) {
    this.synchronizeSingle(el);
  }, this);

  groups.forEach(function(el) {
    this.listenForGroupChanges_(el);
  }, this);
};

/**
 * Destroy single layer.
 * @param {ol.layer.Layer} layer
 * @private
 */
olcs.AbstractSynchronizer.prototype.removeAndDestroySingleLayer_ =
    function(layer) {
  var uid = goog.getUid(layer);
  var counterpart = this.layerMap[uid];
  if (!!counterpart) {
    this.removeSingleCesiumObject(counterpart, false);
    this.destroyCesiumObject(counterpart);
  }
  delete this.layerMap[uid];
};

/**
 * Destroy single group.
 * @param {ol.layer.Group} group
 * @private
 */
olcs.AbstractSynchronizer.prototype.unlistenSingleGroup_ =
    function(group) {
  if (group === this.mapLayerGroup) {
    return;
  }
  var uid = goog.getUid(group);
  var keys = this.olGroupListenKeys_[uid];
  keys.forEach(function(key) {
    ol.Observable.unByKey(key);
  });
  delete this.olGroupListenKeys_[uid];
};

/**
 * Remove layer.
 * @param {ol.layer.Base} removeRoot
 * @private
 */
olcs.AbstractSynchronizer.prototype.removeLayer_ = function(removeRoot) {
  if (!removeRoot) {
    return;
  }
  var layers = [];
  var groups = [];
  olcs.AbstractSynchronizer.flattenLayers_(removeRoot, layers, groups);

  layers.forEach(function(el) {
    this.removeAndDestroySingleLayer_(el);
  }, this);

  groups.forEach(function(el) {
    this.unlistenSingleGroup_(el);
  }, this);
};

/**
 * Synchronizes single layer.
 * @param {ol.layer.Group} group
 * @private
 */
olcs.AbstractSynchronizer.prototype.listenForGroupChanges_ = function(group) {
  var uuid = goog.getUid(group);

  if (!goog.isDef(this.olGroupListenKeys_[uuid])) {
    var listenKeyArray = [];
    this.olGroupListenKeys_[uuid] = listenKeyArray;

    // only the keys that need to be relistened when collection changes
    var contentKeys = [];
    var listenAddRemove = goog.bind(function() {
      var collection = group.getLayers();
      if (goog.isDef(collection)) {
        var handleContentChange_ = goog.bind(function(e) {
          // TODO: should remove the subtree
          // should synchronize the subtree
          this.synchronize_();
        }, this);
        contentKeys = [
          collection.on('add', handleContentChange_),
          collection.on('remove', function(event) {
            this.removeLayer_(event.element);
          }, this)
        ];
        listenKeyArray.push.apply(listenKeyArray, contentKeys);
      }
    }, this);
    listenAddRemove();

    listenKeyArray.push(group.on('change:layers', function(e) {
      goog.array.forEach(contentKeys, function(el) {
        goog.array.remove(listenKeyArray, el);
        ol.Observable.unByKey(el);
      });
      listenAddRemove();
    }));
  }

};

/**
 * Synchronizes single layer.
 * @param {ol.layer.Base} olLayer
 * @protected
 */
olcs.AbstractSynchronizer.prototype.synchronizeSingle = function(olLayer) {
  if (goog.isNull(olLayer)) {
    return;
  }
  var olLayerId = goog.getUid(olLayer);

  // handle layer groups
  goog.asserts.assert(!(olLayer instanceof ol.layer.Group));
  goog.asserts.assert(olLayer instanceof ol.layer.Layer);

  var cesiumObject = this.layerMap[olLayerId];

  // no mapping -> create new layer and set up synchronization
  if (!goog.isDef(cesiumObject)) {
    cesiumObject = this.createSingleCounterpart(olLayer);
    this.layerMap[olLayerId] = cesiumObject;
  }

  // add Cesium layers
  if (goog.isDefAndNotNull(cesiumObject)) {
    this.addCesiumObject(cesiumObject);
  }
};

/**
 * Destroys all the created Cesium objects.
 * @protected
 */
olcs.AbstractSynchronizer.prototype.destroyAll = function() {
  this.removeAllCesiumObjects(true); // destroy
  goog.object.forEach(this.olGroupListenKeys, ol.Observable.unByKey);
  this.olGroupListenKeys = {};
  this.layerMap = {};
};

/**
 * Adds a single Cesium object to the collection.
 * @param {!T} object
 * @protected
 */
olcs.AbstractSynchronizer.prototype.addCesiumObject = goog.abstractMethod;

/**
 * @param {!T} object
 * @protected
 */
olcs.AbstractSynchronizer.prototype.destroyCesiumObject = goog.abstractMethod;

/**
 * Remove single Cesium object from the collection.
 * @param {!T} object
 * @param {boolean} destroy
 * @protected
 */
olcs.AbstractSynchronizer.prototype.removeSingleCesiumObject =
    goog.abstractMethod;

/**
 * Remove all Cesium objects from the collection.
 * @param {boolean} destroy
 * @protected
 */
olcs.AbstractSynchronizer.prototype.removeAllCesiumObjects =
    goog.abstractMethod;

/**
 * @param {!ol.layer.Layer} olLayer
 * @return {T}
 * @protected
 */
olcs.AbstractSynchronizer.prototype.createSingleCounterpart =
    goog.abstractMethod;
<|endoftext|>


<commit_before>var RtmClient = require('@slack/client').RtmClient,
    WebClient = require('@slack/client').WebClient,
    CLIENT_EVENTS = require('@slack/client').CLIENT_EVENTS,
    RTM_EVENTS = require('@slack/client').RTM_EVENTS;

var BaseAdapter = require('../../base_adapter'),
    settings = require('../../models/settings').sharedInstance(),
    logger = require('../../utils/logger')('SlackAdapter');

var SlackAdapter = function(bot) {
    BaseAdapter.call(this, bot);
    this.users = {};
    this.channels = {};
    this.dms = {};
    this.dmForUser = {};
};

SlackAdapter.prototype = {
    setup: function() {
        return new Promise((resolve, reject) => {
            if(!settings.token) {
                logger.error('Cannot use SlackAdapter without a proper token defined in your settings file.');
                reject();
            }
            this.web = new WebClient(settings.token);
            this.rtm = new RtmClient(settings.token);
            this.rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, (data) => {
                data.ims.forEach(d => {
                    this.dms[d.id] = d;
                    this.dmForUser[d.user] = d;
                });
                this.bot.mentionMarks = [`<@${data.self.id}>`, data.self.name].concat(settings.nicknames);
                this.bot.name = data.self.name;
                var proms = [];
                proms = proms.concat(data.channels.map(c => {
                    return this.db.Channel.fromSlackData(c)
                        .then((c) => {
                            this.channels[c.id] = c;
                        });
                }));
                proms = proms.concat(data.groups.forEach(g => {
                    return this.db.Channel.fromSlackData(g)
                        .then((g) => {
                            this.channels[g.id] = g;
                        });
                }));
                proms = proms.concat(data.users.forEach(u => {
                    this.db.User.fromSlackData(u)
                        .then((u) => {
                            this.users[u.id] = u;
                        });
                }));
                Promise.all(proms)
                    .then(resolve)
                    .catch(reject);
            });
            this.rtm.start();
        });
    },
    run: function() {
        logger.debug('Running.');
        this.rtm.on(RTM_EVENTS.MESSAGE, (message) => {
            if(message.type !== 'message' || !!message.subtype) { return; }
            if(message.channel[0] === 'D') {
                message.text = this.bot.name + ': ' + message.text;
            }
            var env = this.makeEnvelope(message.text, message, this.users[message.user], this.channels[message.channel] || this.dms[message.channel]);
            this.receive(env);
        });
    },
    dmForUser: function(u) {
        if(typeof u !== 'string') {
            u = u.id;
        }
        return this.dmForUser[u];
    },
    channelIdForChannel: function(c) {
        return c.id ? c.id : c;
    },
    contextlessSend: function(target, string) {
        var id;
        if(target.id[0] === 'U') {
            id = this.dmForUser(target.id);
        } else {
            id = target.id;
        }
        if(!id) {
            logger.warning('Cannot determine target for item:');
            logger.warning(target);
            return Promise.reject();
        } else {
            return new Promise((resolve) => {
                this.rtm.sendMessage(string, id, function(d) {
                    resolve(d);
                });
            });
        }
    },
    send: function(envelope, string) {
        return new Promise((resolve) => {
            this.rtm.sendMessage(string, envelope.channel.id, (e, d) => {
                resolve(d);
            });
        });
    },
    reply: function(envelope, string) {
        if(envelope.channel.id[0] !== 'D') {
            var breaksLine = string.trim()[0] === '>' ? '\n' : ' ';
            string = '<@' + envelope.user.id + '>:' + breaksLine + string;
        }
        return this.send(envelope, string);
    },
    getMentionTagForUser: function(user) {
        return '<@' + user.id + '>';
    },
    removeMessage: function(messageId, channel) {
        return this.web.chat.delete(messageId, channel);
    },
    messageShouldBeUsedInContext: function(envelope) {
        if(envelope.channel.id[0] !== 'D') {
            return this.bot.inputManager.doesTextDirectlyMentionBot(envelope.text);
        } else {
            return true;
        }
    },
    sendTypingState: function(envelope) {
        var channel = envelope.channel;
        if(typeof channel === 'object') {
            channel = channel.id;
        }
        this.rtm.sendTyping(channel);
    },
    addReaction: function(envelope, reaction) {
        var channel = envelope.channel.id,
            ts = envelope.message.ts;
        return new Promise((resolve, reject) => {
            this.web.reactions.add(reaction, {
                channel: channel,
                timestamp: ts
            }, (err, res) => {
                if(err || !res.ok) {
                    return reject(res);
                };
                resolve();
            });
        });
    }
};

BaseAdapter.setup(SlackAdapter);

module.exports = SlackAdapter;
<commit_msg>Add guard to RTM_EVENTS.MESSAGE event<commit_after>var RtmClient = require('@slack/client').RtmClient,
    WebClient = require('@slack/client').WebClient,
    CLIENT_EVENTS = require('@slack/client').CLIENT_EVENTS,
    RTM_EVENTS = require('@slack/client').RTM_EVENTS;

var BaseAdapter = require('../../base_adapter'),
    settings = require('../../models/settings').sharedInstance(),
    logger = require('../../utils/logger')('SlackAdapter');

var SlackAdapter = function(bot) {
    BaseAdapter.call(this, bot);
    this.users = {};
    this.channels = {};
    this.dms = {};
    this.dmForUser = {};
};

SlackAdapter.prototype = {
    setup: function() {
        return new Promise((resolve, reject) => {
            if(!settings.token) {
                logger.error('Cannot use SlackAdapter without a proper token defined in your settings file.');
                reject();
            }
            this.web = new WebClient(settings.token);
            this.rtm = new RtmClient(settings.token);
            this.rtm.on(CLIENT_EVENTS.RTM.AUTHENTICATED, (data) => {
                data.ims.forEach(d => {
                    this.dms[d.id] = d;
                    this.dmForUser[d.user] = d;
                });
                this.bot.mentionMarks = [`<@${data.self.id}>`, data.self.name].concat(settings.nicknames);
                this.bot.name = data.self.name;
                var proms = [];
                proms = proms.concat(data.channels.map(c => {
                    return this.db.Channel.fromSlackData(c)
                        .then((c) => {
                            this.channels[c.id] = c;
                        });
                }));
                proms = proms.concat(data.groups.forEach(g => {
                    return this.db.Channel.fromSlackData(g)
                        .then((g) => {
                            this.channels[g.id] = g;
                        });
                }));
                proms = proms.concat(data.users.forEach(u => {
                    this.db.User.fromSlackData(u)
                        .then((u) => {
                            this.users[u.id] = u;
                        });
                }));
                Promise.all(proms)
                    .then(resolve)
                    .catch(reject);
            });
            this.rtm.start();
        });
    },
    run: function() {
        if(!this.messageEventSet) {
            this.rtm.on(RTM_EVENTS.MESSAGE, (message) => {
                if(message.type !== 'message' || !!message.subtype) { return; }
                if(message.channel[0] === 'D') {
                    message.text = this.bot.name + ': ' + message.text;
                }
                var env = this.makeEnvelope(message.text, message, this.users[message.user], this.channels[message.channel] || this.dms[message.channel]);
                this.receive(env);
            });
            this.messageEventSet = true;
        }
    },
    dmForUser: function(u) {
        if(typeof u !== 'string') {
            u = u.id;
        }
        return this.dmForUser[u];
    },
    channelIdForChannel: function(c) {
        return c.id ? c.id : c;
    },
    contextlessSend: function(target, string) {
        var id;
        if(target.id[0] === 'U') {
            id = this.dmForUser(target.id);
        } else {
            id = target.id;
        }
        if(!id) {
            logger.warning('Cannot determine target for item:');
            logger.warning(target);
            return Promise.reject();
        } else {
            return new Promise((resolve) => {
                this.rtm.sendMessage(string, id, function(d) {
                    resolve(d);
                });
            });
        }
    },
    send: function(envelope, string) {
        return new Promise((resolve) => {
            this.rtm.sendMessage(string, envelope.channel.id, (e, d) => {
                resolve(d);
            });
        });
    },
    reply: function(envelope, string) {
        if(envelope.channel.id[0] !== 'D') {
            var breaksLine = string.trim()[0] === '>' ? '\n' : ' ';
            string = '<@' + envelope.user.id + '>:' + breaksLine + string;
        }
        return this.send(envelope, string);
    },
    getMentionTagForUser: function(user) {
        return '<@' + user.id + '>';
    },
    removeMessage: function(messageId, channel) {
        return this.web.chat.delete(messageId, channel);
    },
    messageShouldBeUsedInContext: function(envelope) {
        if(envelope.channel.id[0] !== 'D') {
            return this.bot.inputManager.doesTextDirectlyMentionBot(envelope.text);
        } else {
            return true;
        }
    },
    sendTypingState: function(envelope) {
        var channel = envelope.channel;
        if(typeof channel === 'object') {
            channel = channel.id;
        }
        this.rtm.sendTyping(channel);
    },
    addReaction: function(envelope, reaction) {
        var channel = envelope.channel.id,
            ts = envelope.message.ts;
        return new Promise((resolve, reject) => {
            this.web.reactions.add(reaction, {
                channel: channel,
                timestamp: ts
            }, (err, res) => {
                if(err || !res.ok) {
                    return reject(res);
                };
                resolve();
            });
        });
    }
};

BaseAdapter.setup(SlackAdapter);

module.exports = SlackAdapter;
<|endoftext|>