<commit_before>/*
Copyright (c) 2011, 2018 Rob Ellis, Chris Umbel, Hugo W.L. ter Doest

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

var _ = require("underscore"),
    Tokenizer = require('../tokenizers/regexp_tokenizer').WordTokenizer,
    tokenizer = new Tokenizer(),
    frequencies = {},
    nrOfNgrams = 0;

exports.setTokenizer = function(t) {
    if(!_.isFunction(t.tokenize))
        throw new Error('Expected a valid Tokenizer');
    tokenizer = t;
};

exports.ngrams = function(sequence, n, startSymbol, endSymbol, stats) {
    return ngrams(sequence, n, startSymbol, endSymbol, stats);
};

exports.bigrams = function(sequence, startSymbol, endSymbol, stats) {
    return ngrams(sequence, 2, startSymbol, endSymbol, stats);
};

exports.trigrams = function(sequence, startSymbol, endSymbol, stats) {
    return ngrams(sequence, 3, startSymbol, endSymbol, stats);
};

exports.multrigrams = function(sequence, n, startSymbol, endSymbol, stats) {
    return ngrams(sequence, n, startSymbol, endSymbol, stats);
};

// Calculates a key (string) that can be used for a map
function arrayToKey(arr) {
  result = "(";
  arr.forEach(function(x) {
    result += x + ", ";
  });
  result = result.substr(0, result.length - 2) + ")";
  return result;
};

// Updates the statistics for the new ngram
function countNgrams(ngram) {
  nrOfNgrams++;
  var key = arrayToKey(ngram);
  if (!frequencies[key]) {
    frequencies[key] = 0;
  }
  frequencies[key]++;
}

// If stats is true, statistics will be returned
var ngrams = function(sequence, n, startSymbol, endSymbol, stats) {
    var result = [];
    frequencies = {};
    nrOfNgrams = 0;

    if (!_(sequence).isArray()) {
        sequence = tokenizer.tokenize(sequence);
    }

    var count = _.max([0, sequence.length - n + 1]);

    // Check for left padding
    if(typeof startSymbol !== "undefined" && startSymbol !== null) {
        // Create an array of (n) start symbols
        var blanks = [];
        for(var i = 0 ; i < n ; i++) {
            blanks.push(startSymbol);
        }

        // Create the left padding
        for(var p = n - 1 ; p > 0 ; p--) {
            // Create a tuple of (p) start symbols and (n - p) words
          var ngram = blanks.slice(0, p).concat(sequence.slice(0, n - p));
          result.push(ngram);
          if (stats) {
            countNgrams(ngram);
          }
        }
    }

    // Build the complete ngrams
    for (var i = 0; i < count; i++) {
        var ngram = sequence.slice(i, i + n);
        result.push(ngram);
        if (stats) {
          countNgrams(ngram);
        }
    }

    // Check for right padding
    if(typeof endSymbol !== "undefined" && endSymbol !== null) {
        // Create an array of (n) end symbols
        var blanks = [];
        for(var i = 0 ; i < n ; i++) {
            blanks.push(endSymbol);
        }

        // create the right padding
        for(var p = n - 1 ; p > 0 ; p--) {
            // Create a tuple of (p) start symbols and (n - p) words
          var ngram = sequence.slice(sequence.length - p, sequence.length).concat(blanks.slice(0, n - p));
          result.push(ngram);
          if (stats) {
            countNgrams(ngram);
          }
        }
    }

    if (stats) {

      // Count frequencies
      var Nr = {};
      Object.keys(frequencies).forEach(function(key) {
        if (!Nr[frequencies[key]]) {
          Nr[frequencies[key]] = 0;
        }
        Nr[frequencies[key]]++;
      });

      // Return the ngrams AND statistics
      return {
        "ngrams": result,
        "frequencies": frequencies,
        "Nr": Nr,
        "numberOfNgrams": nrOfNgrams
      };

    }
    else { // Do not break existing API of this module
      return result;
    }
};<commit_msg>Fix "_ is not a function" error when webpacked (#554)<commit_after>/*
Copyright (c) 2011, 2018 Rob Ellis, Chris Umbel, Hugo W.L. ter Doest

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

var _ = require("underscore"),
    Tokenizer = require('../tokenizers/regexp_tokenizer').WordTokenizer,
    tokenizer = new Tokenizer(),
    frequencies = {},
    nrOfNgrams = 0;

exports.setTokenizer = function(t) {
    if(!_.isFunction(t.tokenize))
        throw new Error('Expected a valid Tokenizer');
    tokenizer = t;
};

exports.ngrams = function(sequence, n, startSymbol, endSymbol, stats) {
    return ngrams(sequence, n, startSymbol, endSymbol, stats);
};

exports.bigrams = function(sequence, startSymbol, endSymbol, stats) {
    return ngrams(sequence, 2, startSymbol, endSymbol, stats);
};

exports.trigrams = function(sequence, startSymbol, endSymbol, stats) {
    return ngrams(sequence, 3, startSymbol, endSymbol, stats);
};

exports.multrigrams = function(sequence, n, startSymbol, endSymbol, stats) {
    return ngrams(sequence, n, startSymbol, endSymbol, stats);
};

// Calculates a key (string) that can be used for a map
function arrayToKey(arr) {
  result = "(";
  arr.forEach(function(x) {
    result += x + ", ";
  });
  result = result.substr(0, result.length - 2) + ")";
  return result;
};

// Updates the statistics for the new ngram
function countNgrams(ngram) {
  nrOfNgrams++;
  var key = arrayToKey(ngram);
  if (!frequencies[key]) {
    frequencies[key] = 0;
  }
  frequencies[key]++;
}

// If stats is true, statistics will be returned
var ngrams = function(sequence, n, startSymbol, endSymbol, stats) {
    var result = [];
    frequencies = {};
    nrOfNgrams = 0;

    if (!_.isArray(sequence)) {
        sequence = tokenizer.tokenize(sequence);
    }

    var count = _.max([0, sequence.length - n + 1]);

    // Check for left padding
    if(typeof startSymbol !== "undefined" && startSymbol !== null) {
        // Create an array of (n) start symbols
        var blanks = [];
        for(var i = 0 ; i < n ; i++) {
            blanks.push(startSymbol);
        }

        // Create the left padding
        for(var p = n - 1 ; p > 0 ; p--) {
            // Create a tuple of (p) start symbols and (n - p) words
          var ngram = blanks.slice(0, p).concat(sequence.slice(0, n - p));
          result.push(ngram);
          if (stats) {
            countNgrams(ngram);
          }
        }
    }

    // Build the complete ngrams
    for (var i = 0; i < count; i++) {
        var ngram = sequence.slice(i, i + n);
        result.push(ngram);
        if (stats) {
          countNgrams(ngram);
        }
    }

    // Check for right padding
    if(typeof endSymbol !== "undefined" && endSymbol !== null) {
        // Create an array of (n) end symbols
        var blanks = [];
        for(var i = 0 ; i < n ; i++) {
            blanks.push(endSymbol);
        }

        // create the right padding
        for(var p = n - 1 ; p > 0 ; p--) {
            // Create a tuple of (p) start symbols and (n - p) words
          var ngram = sequence.slice(sequence.length - p, sequence.length).concat(blanks.slice(0, n - p));
          result.push(ngram);
          if (stats) {
            countNgrams(ngram);
          }
        }
    }

    if (stats) {

      // Count frequencies
      var Nr = {};
      Object.keys(frequencies).forEach(function(key) {
        if (!Nr[frequencies[key]]) {
          Nr[frequencies[key]] = 0;
        }
        Nr[frequencies[key]]++;
      });

      // Return the ngrams AND statistics
      return {
        "ngrams": result,
        "frequencies": frequencies,
        "Nr": Nr,
        "numberOfNgrams": nrOfNgrams
      };

    }
    else { // Do not break existing API of this module
      return result;
    }
};
<|endoftext|>


<commit_before>'use strict'

var fs = require('fs'),
    path = require('path'),
    util = require('util')

var glob = require('glob')
var cmd = require('cmd-util')
var UglifyJS = require('uglify-js')
var mkdirp = require('mkdirp')

var helper = require('./lib/helper')
var Module = require('./lib/module')

function getMetas(id, base) {
    var metas = []
    var isFile = fs.statSync(path.join(base, id)).isFile()

    metas = glob.sync(isFile ? id : path.join(id, '**'), {
        cwd: base
    }).filter(function(id) {
        return fs.statSync(path.join(base, id)).isFile()
    }).map(function(id) {
        return {
            id: id.replace(/\.js$/, ''),
            originId: id,
            uri: path.join(base, id)
        }
    })
    return metas
}

function builder(input, options) {
    console.log('BUILDING: %s', helper.color(input))

    var options = helper.extend({}, builder.defaults, options)

    Module.config(options)

    var metas = getMetas(input, options.base)

    metas.forEach(function(meta) {
        var logText = 'BUILD TO %s OK.'
        var dest = path.join(options.base, options.dest, meta.originId)

        dest = cmd.iduri.appendext(dest)

        if (path.extname(meta.uri) === '.js') {
            var mod = Module.get(meta)

            if (mod.factory) {
                builder.saveFile(dest, mod.factory, logText)
            }
        } else {
            var file = fs.readFileSync(meta.uri)

            if (file) {
                logText = 'COPY TO %s OK.'
                builder.saveFile(dest, file, logText)
            }
        }
    })
}

helper.extend(builder, {
    defaults: {
        base: './',
        dest: 'sea-modules'
    },
    UglifyJS: UglifyJS,
    Module: Module,
    saveFile: function(filepath, file, logText) {
        mkdirp.sync(path.dirname(filepath))
        fs.writeFileSync(filepath, file)
        if (!logText) {
            logText = 'SAVE %s OK.'
        }
        console.log(logText, helper.color(cmd.iduri.normalize(filepath)))
    }
})

module.exports = builder
<commit_msg>use glob filepath<commit_after>'use strict'

var fs = require('fs'),
    path = require('path'),
    util = require('util')

var glob = require('glob')
var cmd = require('cmd-util')
var UglifyJS = require('uglify-js')
var mkdirp = require('mkdirp')

var helper = require('./lib/helper')
var Module = require('./lib/module')

function getMetas(id, base) {
    var metas = []

    metas = glob.sync(id, {
        cwd: base
    }).filter(function(id) {
        return fs.statSync(path.join(base, id)).isFile()
    }).map(function(id) {
        return {
            id: id.replace(/\.js$/, ''),
            originId: id,
            uri: path.join(base, id)
        }
    })
    return metas
}

function builder(input, options) {
    console.log('BUILDING: %s', helper.color(input))

    var options = helper.extend({}, builder.defaults, options)

    Module.config(options)

    var metas = getMetas(input, options.base)

    metas.forEach(function(meta) {
        var logText = 'BUILD TO %s OK.'
        var dest = path.join(options.base, options.dest, meta.originId)

        dest = cmd.iduri.appendext(dest)

        if (path.extname(meta.uri) === '.js') {
            var mod = Module.get(meta)

            if (mod.factory) {
                builder.saveFile(dest, mod.factory, logText)
            }
        }

        if (!options.ignoreOthers) {
            var file = fs.readFileSync(meta.uri)

            if (file) {
                logText = 'COPY TO %s OK.'
                builder.saveFile(dest, file, logText)
            }
        }
    })
}

helper.extend(builder, {
    defaults: {
        base: './',
        dest: 'sea-modules'
    },
    UglifyJS: UglifyJS,
    Module: Module,
    saveFile: function(filepath, file, logText) {
        mkdirp.sync(path.dirname(filepath))
        fs.writeFileSync(filepath, file)
        if (!logText) {
            logText = 'SAVE %s OK.'
        }
        console.log(logText, helper.color(cmd.iduri.normalize(filepath)))
    }
})

module.exports = builder
<|endoftext|>


<commit_before>var EventEmitter = require('events').EventEmitter;
var Winston = require("winston");
var Util = require("util");
var ZeroMQ = require("zmq");
var msgpack = require('msgpack');
var Promise = require('promise');

function RPCException(message, code) {
    this.code = code || 0;
    this.message = message;
    this.toString = function () {
        return this.message;
    };
}

function _send_response(data, client_id) {
    var self = this;
    var response = msgpack.pack(data);
    if (client_id) {
        self.logger.debug(Util.format(
            "0MQ [%s] => %s: %s",
            data.id, client_id.toString('base64'), JSON.stringify(data)
        ));
        self.socket.send([client_id, response]);
    }
    else {
        self.logger.debug(Util.format(
            "0MQ [%s] => %s",
            data.id, JSON.stringify(data)
        ));
        self.socket.send(response);
    }
}

function _process(data, client_id) {
    var request;
    var self = this;

    try {
        request = msgpack.unpack(data);
        self.logger.debug(Util.format(
            "0MQ [%s] <= %s", request.id || "N/A", JSON.stringify(request)
        ));
    }
    catch (e) {
        _send_response.apply(self, [{
            jsonrpc:'2.0',
            error: {
                code: -32700,
                message: 'Parse error'
            }
        }, client_id]);
        return;
    }

    if (request.jsonrpc !== '2.0') {
        _send_response.apply(self, [{
            jsonrpc:'2.0',
            error: {
                code: -32600,
                message: 'Invalid Request'
            }
        }, client_id]);
        return;
    }

    if (request.hasOwnProperty('method')) {

        function _response(e, result) {

            if (e) {
                if (request.id) {
                    _send_response.apply(self, [{
                        jsonrpc: "2.0",
                        error: {
                            code: e.code || -32603,
                            message: e.message || "Internal Error"
                        },
                        id: request.id
                    }, client_id]);
                }
            }
            else {
                if (result !== undefined && request.id) {
                    _send_response.apply(self, [{
                        jsonrpc: "2.0",
                        result: result,
                        id: request.id
                    }, client_id]);
                }

            }

        }

        var cb = self._callings[request.method];
        if (!cb) _response({code: -32601, message: 'Method not found'});

        try {
            var result = cb.apply(self, [request.params, client_id ? client_id.toString('base64') : null]);
        } catch (e) {
            if (e instanceof RPCException) {
                _response(e);
            } else {
                throw e;
            }
        }

        if (typeof(result) == 'function') {
            // deferred callback
            result(_response);
        } else {
            _response(null, result);
        }

    } else if (request.id && self.promisedRequests.hasOwnProperty(request.id)) {
        var rq = self.promisedRequests[request.id];
        clearTimeout(rq.timeout);
        delete self.promisedRequests[request.id];

        if (request.hasOwnProperty('result')) {
            self.logger.debug(Util.format(
                "0MQ remote: %s(%s) <= %s",
                rq.method, JSON.stringify(rq.params),
                JSON.stringify(request.result)
            ));
            rq.resolve(request.result);
        }
        else if (request.hasOwnProperty('error')) {
            self.logger.debug(Util.format(
                "0MQ remote: %s(%s) <= %s",
                rq.method, JSON.stringify(rq.params),
                JSON.stringify(request.error)
            ));
            rq.reject(request.error);
        }
        else {
            rq.reject({
                code: -32603,
                message: "Internal Error"
            });
        }
    }
    else {
        _send_response.apply(self, [{
            jsonrpc:'2.0',
            error: {
                code: -32600,
                message: 'Invalid Request'
            }
        }, client_id]);
    }
}

var RPC = function (path) {
    this.promisedRequests = {};
    this._callings = {};
    this.callTimeout = 5000;
    this.isServer = false;
    this.Exception = RPCException;
    this.logger = new Winston.Logger();
}

RPC.prototype.bind = function (path) {

    var self = this;
    self.isServer = true;

    var socket = ZeroMQ.socket("router");
    self.socket = socket;

    socket
    .bind(path, function (err) {
        if (err) throw err;

        socket
        .on("message", function (id, data) {
            _process.apply(self, [data, id]);
        });

    });

    return self;
};

RPC.prototype.connect = function (path) {

    var self = this;
    self.isServer = false;

    var socket = ZeroMQ.socket("dealer");

    self.socket = socket;
    socket.connect(path);

    socket
    .on("error", function (err){
        self.logger.debug(Util.format("0MQ error: %s", err));
        // reconnect in 3s
        setTimeout(function (){
            socket.connect(path);
        }, 3000);
    })
    .on("message", function (data) {
        _process.apply(self, [data]);
    });

    return self;
};

// inherit EventEmitter
RPC.prototype.__proto__ = EventEmitter.prototype;

var _uniqsec = 0;
var _uniqid = 0;
var Moment = require('moment');

RPC.prototype.getUniqueId = function () {
    // var uuid = require("uuid");
    // var buffer = new Buffer(16);
    // uuid.v4(null, buffer);
    // return buffer.toString("hex");
    var sec = Moment().valueOf();
    if (sec !== _uniqsec) {
        _uniqsec = sec;
        _uniqid  = 0;
    }
    else {
        _uniqid ++;
    }
    return _uniqsec.toString(36) + _uniqid.toString();
}

RPC.prototype.calling = function (method, cb) {
    var self = this;
    self._callings[method] = cb;
    return self;
}

RPC.prototype.removeCalling = function (key) {
    var self = this;
    if (self._callings.hasOwnProperty(key)) delete self._callings[key];
    return self;
}

RPC.prototype.removeCallings = function (pattern) {
    var self = this;
    var wildcard = require('wildcard');

    wildcard(pattern, Object.keys(self._callings)).forEach(function (key){
        delete self._callings[key];
    });

    return self;
}

RPC.prototype.call = function (method, params, client_id) {

    var self = this;

    return new Promise(function(resolve, reject) {

        var id = self.getUniqueId();

        params = params || {};

        var data = {
            jsonrpc:'2.0',
            method: method,
            params: params,
            id: id
        };

        if (client_id) {
            self.logger.debug(Util.format("0MQ [%s] => [%s] %s", id, client_id, JSON.stringify(data)));
        }
        else {
            self.logger.debug(Util.format("0MQ [%s] => %s", id, JSON.stringify(data)));
        }

        var msg = msgpack.pack(data)
        if (self.isServer) {
            self.socket.send([new Buffer(client_id, 'base64'), msg]);
        }
        else {
            self.socket.send(msg);
        }

        self.promisedRequests[id] = {
            method: method,
            params: params,
            resolve: resolve,
            reject: reject
        };

        self.promisedRequests[id].timeout = setTimeout(function (){
            self.logger.debug(Util.format("0MQ [%s] <= timeout", id));
            delete self.promisedRequests[id];
            reject({
                code: -32603,
                message: "Call Timeout"
            });
        }, self.callTimeout);

    })

};

var RPCWrapper = function() {
    return new RPC();
};

RPCWrapper.connect = function (path) {
    var rpc = new RPC();
    return rpc.connect(path);
};

RPCWrapper.bind = function (path) {
    var rpc = new RPC();
    return rpc.bind(path);
};

module.exports = RPCWrapper;

<commit_msg>add calling("*") to handle all undefined methods<commit_after>var EventEmitter = require('events').EventEmitter;
var Winston = require("winston");
var Util = require("util");
var ZeroMQ = require("zmq");
var msgpack = require('msgpack');
var Promise = require('promise');

function RPCException(message, code) {
    this.code = code || 0;
    this.message = message;
    this.toString = function () {
        return this.message;
    };
}

function _send_response(data, client_id) {
    var self = this;
    var response = msgpack.pack(data);
    if (client_id) {
        self.logger.debug(Util.format(
            "0MQ [%s] => %s: %s",
            data.id, client_id.toString('base64'), JSON.stringify(data)
        ));
        self.socket.send([client_id, response]);
    }
    else {
        self.logger.debug(Util.format(
            "0MQ [%s] => %s",
            data.id, JSON.stringify(data)
        ));
        self.socket.send(response);
    }
}

function _process(data, client_id) {
    var request;
    var self = this;

    try {
        request = msgpack.unpack(data);
        self.logger.debug(Util.format(
            "0MQ [%s] <= %s", request.id || "N/A", JSON.stringify(request)
        ));
    }
    catch (e) {
        _send_response.apply(self, [{
            jsonrpc:'2.0',
            error: {
                code: -32700,
                message: 'Parse error'
            }
        }, client_id]);
        return;
    }

    if (request.jsonrpc !== '2.0') {
        _send_response.apply(self, [{
            jsonrpc:'2.0',
            error: {
                code: -32600,
                message: 'Invalid Request'
            }
        }, client_id]);
        return;
    }

    if (request.hasOwnProperty('method')) {
        /** request */

        function _response(e, result) {

            if (e) {
                if (request.id) {
                    _send_response.apply(self, [{
                        jsonrpc: "2.0",
                        error: {
                            code: e.code || -32603,
                            message: e.message || "Internal Error"
                        },
                        id: request.id
                    }, client_id]);
                }
            }
            else {
                if (result !== undefined && request.id) {
                    _send_response.apply(self, [{
                        jsonrpc: "2.0",
                        result: result,
                        id: request.id
                    }, client_id]);
                }

            }

        }

        var params = request.params;

        var cb = self._callings[request.method];
        if (!cb) {
            if (self._callings['*']) {
                /**
                 * 如果 method not found, 但有 * handler,
                 * 则使用 * handler
                 */
                cb = self._callings['*'];
                params = {
                    method: request.method,
                    params: request.params
                };
            }
            else {
                _response({code: -32601, message: 'Method not found'});
            }
        }

        try {
            var result = cb.apply(self, [params, client_id ? client_id.toString('base64') : null]);
        } catch (e) {

            if (e instanceof RPCException) {
                _response(e);
            } else {
                throw e;
            }
        }

        if (typeof(result) == 'function') {
            // deferred callback
            result(_response);
        } else {
            _response(null, result);
        }

    } else if (request.id && self.promisedRequests.hasOwnProperty(request.id)) {
        /** response */

        var rq = self.promisedRequests[request.id];
        clearTimeout(rq.timeout);
        delete self.promisedRequests[request.id];

        if (request.hasOwnProperty('result')) {
            self.logger.debug(Util.format(
                "0MQ remote: %s(%s) <= %s",
                rq.method, JSON.stringify(rq.params),
                JSON.stringify(request.result)
            ));
            rq.resolve(request.result);
        }
        else if (request.hasOwnProperty('error')) {
            self.logger.debug(Util.format(
                "0MQ remote: %s(%s) <= %s",
                rq.method, JSON.stringify(rq.params),
                JSON.stringify(request.error)
            ));
            rq.reject(request.error);
        }
        else {
            rq.reject({
                code: -32603,
                message: "Internal Error"
            });
        }
    }
    else {
        _send_response.apply(self, [{
            jsonrpc:'2.0',
            error: {
                code: -32600,
                message: 'Invalid Request'
            }
        }, client_id]);
    }
}

var RPC = function (path) {
    this.promisedRequests = {};
    this._callings = {};
    this.callTimeout = 5000;
    this.isServer = false;
    this.Exception = RPCException;
    this.logger = new Winston.Logger();
}

RPC.prototype.bind = function (path) {

    var self = this;
    self.isServer = true;

    var socket = ZeroMQ.socket("router");
    self.socket = socket;

    socket
    .bind(path, function (err) {
        if (err) throw err;

        socket
        .on("message", function (id, data) {
            _process.apply(self, [data, id]);
        });

    });

    return self;
};

RPC.prototype.connect = function (path) {

    var self = this;
    self.isServer = false;

    var socket = ZeroMQ.socket("dealer");

    self.socket = socket;
    socket.connect(path);

    socket
    .on("error", function (err){
        self.logger.debug(Util.format("0MQ error: %s", err));
        // reconnect in 3s
        setTimeout(function (){
            socket.connect(path);
        }, 3000);
    })
    .on("message", function (data) {
        _process.apply(self, [data]);
    });

    return self;
};

// inherit EventEmitter
RPC.prototype.__proto__ = EventEmitter.prototype;

var _uniqsec = 0;
var _uniqid = 0;
var Moment = require('moment');

RPC.prototype.getUniqueId = function () {
    // var uuid = require("uuid");
    // var buffer = new Buffer(16);
    // uuid.v4(null, buffer);
    // return buffer.toString("hex");
    var sec = Moment().valueOf();
    if (sec !== _uniqsec) {
        _uniqsec = sec;
        _uniqid  = 0;
    }
    else {
        _uniqid ++;
    }
    return _uniqsec.toString(36) + _uniqid.toString();
}

RPC.prototype.calling = function (method, cb) {
    var self = this;
    self._callings[method] = cb;
    return self;
}

RPC.prototype.removeCalling = function (key) {
    var self = this;
    if (self._callings.hasOwnProperty(key)) delete self._callings[key];
    return self;
}

RPC.prototype.removeCallings = function (pattern) {
    var self = this;
    var wildcard = require('wildcard');

    wildcard(pattern, Object.keys(self._callings)).forEach(function (key){
        delete self._callings[key];
    });

    return self;
}

RPC.prototype.call = function (method, params, client_id) {

    var self = this;

    return new Promise(function(resolve, reject) {

        var id = self.getUniqueId();

        params = params || {};

        var data = {
            jsonrpc:'2.0',
            method: method,
            params: params,
            id: id
        };

        if (client_id) {
            self.logger.debug(Util.format("0MQ [%s] => [%s] %s", id, client_id, JSON.stringify(data)));
        }
        else {
            self.logger.debug(Util.format("0MQ [%s] => %s", id, JSON.stringify(data)));
        }

        var msg = msgpack.pack(data)
        if (self.isServer) {
            self.socket.send([new Buffer(client_id, 'base64'), msg]);
        }
        else {
            self.socket.send(msg);
        }

        self.promisedRequests[id] = {
            method: method,
            params: params,
            resolve: resolve,
            reject: reject
        };

        self.promisedRequests[id].timeout = setTimeout(function (){
            self.logger.debug(Util.format("0MQ [%s] <= timeout", id));
            delete self.promisedRequests[id];
            reject({
                code: -32603,
                message: "Call Timeout"
            });
        }, self.callTimeout);

    })

};

var RPCWrapper = function() {
    return new RPC();
};

RPCWrapper.connect = function (path) {
    var rpc = new RPC();
    return rpc.connect(path);
};

RPCWrapper.bind = function (path) {
    var rpc = new RPC();
    return rpc.bind(path);
};

module.exports = RPCWrapper;

<|endoftext|>


<commit_before>var production = process.env.NODE_ENV === 'production';
var nodeVersion = parseFloat(process.versions.node);
/* istanbul ignore next */
if (nodeVersion >= 7.6)
  return module.exports = require('./lib-node7' + (production ? '' : '-dev') + '/index');
/* istanbul ignore next */
if (nodeVersion >= 6.5)
  return module.exports = require('./lib-node6' + (production ? '' : '-dev') + '/index');
/* istanbul ignore next */
return module.exports = require('./lib-older-node' + (production ? '' : '-dev') + '/index');
<commit_msg>fix: node 6.10<commit_after>var production = process.env.NODE_ENV === 'production';
var nodeVersion = process.versions.node.split('.');
var nodeVersionMajor = Number(nodeVersion[0]);
var nodeVersionMinor = Number(nodeVersion[1]);
/* istanbul ignore next */
if (nodeVersionMajor > 7 || (nodeVersionMajor === 7 && nodeVersionMinor >= 6))
  return module.exports = require('./lib-node7' + (production ? '' : '-dev') + '/index');
/* istanbul ignore next */
if (nodeVersionMajor > 6 || (nodeVersionMajor === 6 && nodeVersionMinor >= 5))
  return module.exports = require('./lib-node6' + (production ? '' : '-dev') + '/index');
/* istanbul ignore next */
return module.exports = require('./lib-older-node' + (production ? '' : '-dev') + '/index');
<|endoftext|>


<commit_before>import {create} from "./vt-js/pkg/vt_js";
import {asciicast} from "./driver/asciicast";
import {phoenixChannel} from "./driver/phoenix_channel";
import {test} from "./driver/test";
import {websocket} from "./driver/websocket";

class AsciinemaPlayerCore {
  // public

  constructor(src, opts) {
    let feed = this.feed.bind(this);

    if (src.driver == 'websocket') {
      this.driver = websocket(src.url, null, null, feed);
    } else if (src.driver == 'phx_chan') {
      this.driver = phoenixChannel(src.socketUrl, src.channelName, null, null, feed);
    } else if (src.driver == 'test') {
      this.driver = test(src.kind, null, null, 1, feed);
    } else if (src.driver == 'asciicast') {
      this.driver = asciicast(src.url, null, null, 1, feed);
    } else {
      throw `unsupported driver: ${JSON.stringify(src)}`;
    }

    this.lines = [];
    this.changedLines = new Set();
  }

  static build(src, opts) {
    if (typeof src === 'string') {
      if (src.substring(0, 5) == 'ws://' || src.substring(0, 6) == 'wss://') {
        src = { driver: 'websocket', url: src };
      } else if (src.substring(0, 7) == 'test://') {
        src = { driver: 'test', kind: src.substring(7) };
      } else {
        src = { driver: 'asciicast', url: src };
      }
    }

    return new AsciinemaPlayerCore(src, opts);
  }

  start() {
    let start = this.driver.start();

    if (!start) {
      start = Promise.resolve({
        width: this.driver.width,
        height: this.driver.height
      });
    }

    return start.then(size => {
      this.vt = create(size.width, size.height);

      for (let i = 0; i < size.height; i++) {
        this.changedLines.add(i);
      }

      return size;
    });
  }

  stop() {
    this.driver.stop();
  }

  getChangedLines() {
    const lines = new Map();

    if (this.vt) {
      for (const i of this.changedLines) {
        lines.set(i, {id: i, segments: this.vt.get_line(i)});
      }

      this.changedLines.clear();
    }

    return lines;
  }

  getCurrentTime() {
    if (this.driver.getCurrentTime) {
      return this.driver.getCurrentTime();
    } else {
      // TODO return time diff between start and now
      return 83;
    }
  }

  // private

  feed(data) {
    const affectedLines = this.vt.feed(data);
    affectedLines.forEach(i => this.changedLines.add(i));
  }
}

export default AsciinemaPlayerCore;
<commit_msg>Reorder driver selection<commit_after>import {create} from "./vt-js/pkg/vt_js";
import {asciicast} from "./driver/asciicast";
import {phoenixChannel} from "./driver/phoenix_channel";
import {test} from "./driver/test";
import {websocket} from "./driver/websocket";

class AsciinemaPlayerCore {
  // public

  constructor(src, opts) {
    let feed = this.feed.bind(this);

    if (src.driver == 'asciicast') {
      this.driver = asciicast(src.url, null, null, 1, feed);
    } else if (src.driver == 'websocket') {
      this.driver = websocket(src.url, null, null, feed);
    } else if (src.driver == 'phx_chan') {
      this.driver = phoenixChannel(src.socketUrl, src.channelName, null, null, feed);
    } else if (src.driver == 'test') {
      this.driver = test(src.kind, null, null, 1, feed);
    } else {
      throw `unsupported driver: ${JSON.stringify(src)}`;
    }

    this.lines = [];
    this.changedLines = new Set();
  }

  static build(src, opts) {
    if (typeof src === 'string') {
      if (src.substring(0, 5) == 'ws://' || src.substring(0, 6) == 'wss://') {
        src = { driver: 'websocket', url: src };
      } else if (src.substring(0, 7) == 'test://') {
        src = { driver: 'test', kind: src.substring(7) };
      } else {
        src = { driver: 'asciicast', url: src };
      }
    }

    return new AsciinemaPlayerCore(src, opts);
  }

  start() {
    let start = this.driver.start();

    if (!start) {
      start = Promise.resolve({
        width: this.driver.width,
        height: this.driver.height
      });
    }

    return start.then(size => {
      this.vt = create(size.width, size.height);

      for (let i = 0; i < size.height; i++) {
        this.changedLines.add(i);
      }

      return size;
    });
  }

  stop() {
    this.driver.stop();
  }

  getChangedLines() {
    const lines = new Map();

    if (this.vt) {
      for (const i of this.changedLines) {
        lines.set(i, {id: i, segments: this.vt.get_line(i)});
      }

      this.changedLines.clear();
    }

    return lines;
  }

  getCurrentTime() {
    if (this.driver.getCurrentTime) {
      return this.driver.getCurrentTime();
    } else {
      // TODO return time diff between start and now
      return 83;
    }
  }

  // private

  feed(data) {
    const affectedLines = this.vt.feed(data);
    affectedLines.forEach(i => this.changedLines.add(i));
  }
}

export default AsciinemaPlayerCore;
<|endoftext|>


<commit_before>var checksum = require('crc-32');
var dns = require('native-dns');
var winston = require('winston');
winston.cli();

if (process.argv.length < 4) {
  console.log('Usage: renamed <domain name> <public IP of server>');
  process.exit();
}
var rootTLD = process.argv[2].toLowerCase();
var myip = process.argv[3];
var server = dns.createServer();
var sessionDigestKey = require('uuid').v4();

var createChecksum = function (client, server) {
  var str = client + '-' + server + '-' + sessionDigestKey;
  str = str.replace(/\./g, 'a').replace(/:/g, 'b');
  return Math.abs(checksum.str(str));
};

// Return [client,server] if query is valid, or false if not.
var validateQuery = function (query) {
  query = query.replace(/a/g, '.').replace(/b/g, ':');
  var parts = query.split('-');
  if (parts.length != 3) {
    return false;
  }
  var client = parts[0];
  var server = parts[1];
  var expectedChecksum = createChecksum(client, server);
  if (parts[2] === '' + expectedChecksum) {
    return [client, server];
  } else {
    return false;
  }
};

var matchServer = function (client) {
  var server = '8.8.8.8'; //todo: roundrobin.
  return server;
};

var createPrefix = function (client, server) {
  var checksum = createChecksum(client, server);
  var prefix = client + '-' + server + '-' + checksum;
  return prefix.replace(/\./g, 'a').replace(/:/g, 'b');
};

server.on('request', function (req, resp) {
  if (req.question.length > 0 && req.question[0].type == 1) {
    var query = req.question[0].name.toLowerCase();
    var addr = req.address.address;
    winston.info(addr + ': ' + query);
    // Failures
    if (query.indexOf(rootTLD) === -1) {
      resp.answer.push(dns.A({
        name: query,
        address: myip,
        ttl: 5
      }));
      resp.send();
      return;
    // Initial query
    } else if (query === "ns1." + rootTLD || query === "ns2." + rootTLD) {
      resp.answer.push(dns.A({
        name: 'ns1.' + rootTLD,
        address: myip,
        ttl: 5
      }));
      resp.answer.push(dns.A({
        name: 'ns2.' + rootTLD,
        address: myip,
        ttl: 5
      }));
      resp.authority.push(dns.SOA({
        name: rootTLD,
        primary: 'ns1' + rootTLD,
        admin: 'measurement.' + rootTLD,
        serial: new Date().valueOf(),
        refresh: 5,
        retry: 5,
        expiration: 5,
        minimum: 5,
        ttl: 5
      }));
      resp.send();
      return;
    } else if (query === rootTLD) {
      // TODO: record attempt at connectivity
      var resolver = matchServer(addr);
      var delegee = createPrefix(addr, resolver) + '.' + rootTLD;
      resp.header.ra = false;
      resp.answer.push(dns.CNAME({
        name: query,
        ttl: 5,
        data: delegee
      }));
      resp.authority.push(dns.SOA({
        name: delegee,
        primary: 'ns1' + delegee,
        admin: 'measurement.' + rootTLD,
        serial: new Date().valueOf(),
        refresh: 5,
        retry: 5,
        expiration: 5,
        minimum: 5,
        ttl: 5
      }));
      resp.additional.push(dns.A({
        name: 'ns1.' + delegee,
        address: resolver,
        ttl: 5
      }));
      resp.send();
      return;
    }

    // Successful indicaton of connectivity
    var prefix = query.split(rootTLD)[0];
    prefix = prefix.substr(0, prefix.length - 1);
    var parts = validateQuery(prefix);
    if (parts === false) {
      resp.answer.push(dns.A({
        name: query,
        address: myip,
        ttl: 5
      }));
      resp.send();
      return;
    } else {
      // Queries for the cname are owned by the appropriate delegee.
      if (addr == parts[0]) {
        resp.authority.push(dns.NS({
          name: query,
          ttl: 5,
          data: 'ns1.' + query
        }));
        resp.additional.push(dns.A({
          name: 'ns1.' + query,
          address: parts[1],
          ttl: 5
        }));
        resp.send();
        return;
      } else {
        // TODO: Log success of client-server. query.
        // TODO: See if it's been made recently by same server, indicating response dropped.
      /*
      resp.answer.push(dns.CNAME({
        name: query,
        ttl: 5,
        data: 'www.google.com'
      }));
      */
        resp.answer.push(dns.A({
          name: query,
          address: addr,
          ttl: 5
        }));
        resp.send();
        return;
      }
    }
  }
});

server.on('error', function (err) {
  console.warn(err.stack);
});

server.serve(53);
winston.info('Running for', rootTLD, myip);
winston.info('Session Key is', sessionDigestKey);
<commit_msg>bind to appropriate interface<commit_after>var checksum = require('crc-32');
var dns = require('native-dns');
var winston = require('winston');
winston.cli();

if (process.argv.length < 4) {
  console.log('Usage: renamed <domain name> <public IP of server>');
  process.exit();
}
var rootTLD = process.argv[2].toLowerCase();
var myip = process.argv[3];
var server = dns.createServer();
var sessionDigestKey = require('uuid').v4();

var createChecksum = function (client, server) {
  var str = client + '-' + server + '-' + sessionDigestKey;
  str = str.replace(/\./g, 'a').replace(/:/g, 'b');
  return Math.abs(checksum.str(str));
};

// Return [client,server] if query is valid, or false if not.
var validateQuery = function (query) {
  query = query.replace(/a/g, '.').replace(/b/g, ':');
  var parts = query.split('-');
  if (parts.length != 3) {
    return false;
  }
  var client = parts[0];
  var server = parts[1];
  var expectedChecksum = createChecksum(client, server);
  if (parts[2] === '' + expectedChecksum) {
    return [client, server];
  } else {
    return false;
  }
};

var matchServer = function (client) {
  var server = '8.8.8.8'; //todo: roundrobin.
  return server;
};

var createPrefix = function (client, server) {
  var checksum = createChecksum(client, server);
  var prefix = client + '-' + server + '-' + checksum;
  return prefix.replace(/\./g, 'a').replace(/:/g, 'b');
};

server.on('request', function (req, resp) {
  if (req.question.length > 0 && req.question[0].type == 1) {
    var query = req.question[0].name.toLowerCase();
    var addr = req.address.address;
    winston.info(addr + ': ' + query);
    // Failures
    if (query.indexOf(rootTLD) === -1) {
      resp.answer.push(dns.A({
        name: query,
        address: myip,
        ttl: 5
      }));
      resp.send();
      return;
    // Initial query
    } else if (query === "ns1." + rootTLD || query === "ns2." + rootTLD) {
      resp.answer.push(dns.A({
        name: 'ns1.' + rootTLD,
        address: myip,
        ttl: 5
      }));
      resp.answer.push(dns.A({
        name: 'ns2.' + rootTLD,
        address: myip,
        ttl: 5
      }));
      resp.authority.push(dns.SOA({
        name: rootTLD,
        primary: 'ns1' + rootTLD,
        admin: 'measurement.' + rootTLD,
        serial: new Date().valueOf(),
        refresh: 5,
        retry: 5,
        expiration: 5,
        minimum: 5,
        ttl: 5
      }));
      resp.send();
      return;
    } else if (query === rootTLD) {
      // TODO: record attempt at connectivity
      var resolver = matchServer(addr);
      var delegee = createPrefix(addr, resolver) + '.' + rootTLD;
      resp.header.ra = false;
      resp.answer.push(dns.CNAME({
        name: query,
        ttl: 5,
        data: delegee
      }));
      resp.authority.push(dns.SOA({
        name: delegee,
        primary: 'ns1' + delegee,
        admin: 'measurement.' + rootTLD,
        serial: new Date().valueOf(),
        refresh: 5,
        retry: 5,
        expiration: 5,
        minimum: 5,
        ttl: 5
      }));
      resp.additional.push(dns.A({
        name: 'ns1.' + delegee,
        address: resolver,
        ttl: 5
      }));
      resp.send();
      return;
    }

    // Successful indicaton of connectivity
    var prefix = query.split(rootTLD)[0];
    prefix = prefix.substr(0, prefix.length - 1);
    var parts = validateQuery(prefix);
    if (parts === false) {
      resp.answer.push(dns.A({
        name: query,
        address: myip,
        ttl: 5
      }));
      resp.send();
      return;
    } else {
      // Queries for the cname are owned by the appropriate delegee.
      if (addr == parts[0]) {
        resp.authority.push(dns.NS({
          name: query,
          ttl: 5,
          data: 'ns1.' + query
        }));
        resp.additional.push(dns.A({
          name: 'ns1.' + query,
          address: parts[1],
          ttl: 5
        }));
        resp.send();
        return;
      } else {
        // TODO: Log success of client-server. query.
        // TODO: See if it's been made recently by same server, indicating response dropped.
      /*
      resp.answer.push(dns.CNAME({
        name: query,
        ttl: 5,
        data: 'www.google.com'
      }));
      */
        resp.answer.push(dns.A({
          name: query,
          address: addr,
          ttl: 5
        }));
        resp.send();
        return;
      }
    }
  }
});

server.on('error', function (err) {
  console.warn(err.stack);
});

server.serve(53, myip);
winston.info('Running for', rootTLD, myip);
winston.info('Session Key is', sessionDigestKey);
<|endoftext|>


<commit_before>/**
 * jsreward
 * Copyright(c) 2015 peter4431
 * MIT Licensed
 *
 * 1. 奖励策划填写,服务端,客户端统一
 * 2. obj,收益表达字符 互转
 * 3. 多个同种奖励自动合并
 * 4. 几率奖励功能
 *
 ### 1. 收益表达式
 * 为了配置和显示处理简单,使用物品字符串表示东西的类型,id,数量等。
 * 金币-1,钻石-2,精力-3,星星-4,道具-5,角色-6,碎片-7,碟片-8,礼包-9
 * 形式为:'type|param1|param2',如下
 1|1000--金币1000
 2|1000--钻石1000个
 3|10  --精力10点
 4|6   --星星6个
 5|1|2 -- id为1的道具两个
 6|2   -- id为2的角色
 7|1|9 -- id为1的碟片的第9个碎片
 8|1   -- id为1的碟片
 9|百度|https://www.baidu.com/img/bd_logo1.png -- 名字为百度的物品

 * 策划填写
 1|1000,5|1|1,7|1|9

 * 概率掉落,最后一个可选字段表示概率
 1|1000,5|1|1,7|1|9|20 -- 金币1000，id为1的道具1个，20%几率掉落碎片1,9

 * 多个物品中随机一个
 1|1000,5|1|1,[7|1|9|20-7|1|8|20]
 -- 金币1000，id为1的道具1个，50%几率掉落碎片1,9,50%几率掉落碎片1,8,20表示权重
 */

var Reward = function(){
  this.countName;
  this.types = {};
  this.selectExp = /^\[.*?\]$/;
  this.intExp = /^[\+\-\.\d]*$/;//check is number

  this.SPLIT_SELECT = '-';
  this.SPLIT_REWARD = ',';
  this.SPLIT_PROPER = '|';this
}

var pro = Reward.prototype;

pro.addType = function (type, name, args) {
  var obj = this.types[type] = {};
  args = Array.prototype.splice.call(arguments, 2);

  obj.type = type;
  obj.name = name;
  obj.args = args;
};

/**
 * 设置定义为数量的属性名,用来合并道具
 * @param name
 */
pro.setCountName = function (name) {
  this.countName = name;
};

pro.parseValue = function (valueStr) {
  if (this.intExp.test(valueStr)) {
    return parseInt(valueStr);
  } else {
    return valueStr;
  }
};

/**
 * 解析 纯|分隔的物品,除定义的属性外允许带几率
 * */
pro.parseRewardBase = function (rewardStr) {
  if (!rewardStr) {
    return null;
  }

  var args = rewardStr.split(this.SPLIT_PROPER);
  var resultObj = {};
  var type = args[0];
  args = args.splice(1);

  var typeObj = this.types[type];
  var typeArgs = typeObj.args;

  resultObj.type = type;
  resultObj.name = typeObj.name;

  var length = Math.min(typeArgs.length, args.length);
  for (var i = 0; i < length; i++) {
    resultObj[typeArgs[i]] = this.parseValue(args[i]);
  }

  if (args.length <= typeArgs.length) {
    return resultObj;
  }

  var ratio = parseInt(args[i]) / 100;
  var mrandom = Math.random();

  if (mrandom < ratio) {
    return resultObj;
  } else {
    return null;
  }
}

/**
 * 按概率选择一个物品
 * */
pro.filterSelect = function (rewardStr) {

  if (!this.selectExp.test(rewardStr)) {
    return rewardStr;
  }

  rewardStr = rewardStr.substr(1, rewardStr.length - 2);

  var rewards = rewardStr.split(this.SPLIT_SELECT);
  var ratios = [];

  var itemStr;
  var itemRatioStr;
  var seperateIndex;
  for (var i = 0; i < rewards.length; i++) {
    itemStr = rewards[i];
    seperateIndex = itemStr.lastIndexOf(this.SPLIT_PROPER);
    itemRatioStr = itemStr.substr(seperateIndex + 1);
    rewards[i] = itemStr.substring(0, seperateIndex);

    ratios.push(parseInt(itemRatioStr));
  }

  var stairs = [];
  var sum = 0;
  for (var i = 0; i < ratios.length; i++) {
    sum = sum + ratios[i];
    stairs.push(sum);
  }

  var resultIndex;
  var mrandom = (Math.random() * sum);
  for (var i = 0; i < stairs.length; i++) {
    if (mrandom < stairs[i]) {
      resultIndex = i;
      break;
    }
  }

  return rewards[resultIndex];
}

/**
 * 过滤空格
 * */
pro.filterSpace = function (rewardStr) {
  rewardStr = rewardStr.replace(/ /g, '');
  return rewardStr;
};

/**
 * 合并奖励的数量
 * 如果调用过 setCountName 设置过数量属性,可以把其他属性都一样的奖励合并到一起
 * @param rewards 奖励数组
 */
pro.mergeCount = function (rewards) {
  // 1. 找到所有包含 countName 的奖励,
  // 2. 然后检查其他属性是否一样,一样则合并到出现的第一个

  if (!this.countName) {
    return;
  }

  var propStrs = [];

  var propStr;
  var item;
  var index;

  for (var i = 0; i < rewards.length; i++) {
    item = rewards[i];

    propStr = this.getPropStrNoCount(item);

    index = propStrs.indexOf(propStr);

    if (index === -1) {
      propStrs.push(propStr);
    } else {

      if (item.hasOwnProperty(this.countName)) {
        rewards.splice(i, 1);
        rewards[index][this.countName] += item[this.countName];
        i--;
      }
    }
  }
};

/**
 * 得到奖励的属性字符串
 */
pro.getPropStrNoCount = function (item) {

  var keys = this.types[item.type].args.concat();

  var indexOfCount = -1;

  while (indexOfCount = keys.indexOf(this.countName), indexOfCount >= 0) {
    keys.splice(indexOfCount, 1);
  }

  keys.push('type');
  keys.sort();

  var arr = [];
  keys.forEach(function (name) {
    arr.push(item[name]);
  });

  return arr.join(',');
};

/**
 * 解析,分隔的字符串
 * */
pro.parseReward = function (rewardStr) {
  rewardStr = this.filterSpace(rewardStr);
  var result = [];
  var rewards = rewardStr.split(this.SPLIT_REWARD);

  var reward;
  var itemStr;
  for (var i = 0; i < rewards.length; i++) {
    itemStr = rewards[i];

    itemStr = this.filterSelect(itemStr);
    reward = this.parseRewardBase(itemStr);
    reward && result.push(reward);
  }

  if (this.countName) {
    this.mergeCount(result);
  }

  return result;

};

/**
 * 奖励转换成字符串
 * @param rewards 奖励数组
 * @return {String}
 */
pro.rewardToStr = function (rewards) {
  var result = [];
  for (var i = 0; i < rewards.length; i++) {
    result.push(this.rewardToStrBase(rewards[i]));
  }

  return result.join(this.SPLIT_REWARD);
};

/**
 * 单个奖励转字符串
 * @param reward
 */
pro.rewardToStrBase = function (reward) {
  var keyArr = ['type'];
  keyArr = keyArr.concat(this.types[reward.type].args);
  var propArr = [];

  keyArr.forEach(function (name) {
    propArr.push(reward[name]);
  })

  return propArr.join(this.SPLIT_PROPER);
};

/**
 * 真正加到玩家身上
 * @param rewardStr 奖励字符串
 * @param oneCallback 一个obj的处理函数
 * */
pro.addReward = function (rewardStr, oneCallback, args) {
  var rewards = this.parseReward(rewardStr);

  var args = Array.prototype.splice.call(arguments, 2);

  for (var i = 0; i < rewards.length; i++) {

    if (oneCallback) {
      var margs = args.concat();
      margs.unshift(rewards[i]);
      oneCallback.apply(null, margs);
    }
  }
  return rewards;
}

module.exports = new Reward();
<commit_msg>增加判空处理<commit_after>/**
 * jsreward
 * Copyright(c) 2015 peter4431
 * MIT Licensed
 *
 * 1. 奖励策划填写,服务端,客户端统一
 * 2. obj,收益表达字符 互转
 * 3. 多个同种奖励自动合并
 * 4. 几率奖励功能
 *
 ### 1. 收益表达式
 * 为了配置和显示处理简单,使用物品字符串表示东西的类型,id,数量等。
 * 金币-1,钻石-2,精力-3,星星-4,道具-5,角色-6,碎片-7,碟片-8,礼包-9
 * 形式为:'type|param1|param2',如下
 1|1000--金币1000
 2|1000--钻石1000个
 3|10  --精力10点
 4|6   --星星6个
 5|1|2 -- id为1的道具两个
 6|2   -- id为2的角色
 7|1|9 -- id为1的碟片的第9个碎片
 8|1   -- id为1的碟片
 9|百度|https://www.baidu.com/img/bd_logo1.png -- 名字为百度的物品

 * 策划填写
 1|1000,5|1|1,7|1|9

 * 概率掉落,最后一个可选字段表示概率
 1|1000,5|1|1,7|1|9|20 -- 金币1000，id为1的道具1个，20%几率掉落碎片1,9

 * 多个物品中随机一个
 1|1000,5|1|1,[7|1|9|20-7|1|8|20]
 -- 金币1000，id为1的道具1个，50%几率掉落碎片1,9,50%几率掉落碎片1,8,20表示权重
 */

var Reward = function(){
  this.countName;
  this.types = {};
  this.selectExp = /^\[.*?\]$/;
  this.intExp = /^[\+\-\.\d]*$/;//check is number

  this.SPLIT_SELECT = '-';
  this.SPLIT_REWARD = ',';
  this.SPLIT_PROPER = '|';this
}

var pro = Reward.prototype;

pro.addType = function (type, name, args) {
  var obj = this.types[type] = {};
  args = Array.prototype.splice.call(arguments, 2);

  obj.type = type;
  obj.name = name;
  obj.args = args;
};

/**
 * 设置定义为数量的属性名,用来合并道具
 * @param name
 */
pro.setCountName = function (name) {
  this.countName = name;
};

pro.parseValue = function (valueStr) {
  if (this.intExp.test(valueStr)) {
    return parseInt(valueStr);
  } else {
    return valueStr;
  }
};

/**
 * 解析 纯|分隔的物品,除定义的属性外允许带几率
 * */
pro.parseRewardBase = function (rewardStr) {
  if (!rewardStr) {
    return null;
  }

  var args = rewardStr.split(this.SPLIT_PROPER);
  var resultObj = {};
  var type = args[0];
  args = args.splice(1);

  var typeObj = this.types[type];
  var typeArgs = typeObj.args;

  resultObj.type = type;
  resultObj.name = typeObj.name;

  var length = Math.min(typeArgs.length, args.length);
  for (var i = 0; i < length; i++) {
    resultObj[typeArgs[i]] = this.parseValue(args[i]);
  }

  if (args.length <= typeArgs.length) {
    return resultObj;
  }

  var ratio = parseInt(args[i]) / 100;
  var mrandom = Math.random();

  if (mrandom < ratio) {
    return resultObj;
  } else {
    return null;
  }
}

/**
 * 按概率选择一个物品
 * */
pro.filterSelect = function (rewardStr) {

  if (!this.selectExp.test(rewardStr)) {
    return rewardStr;
  }

  rewardStr = rewardStr.substr(1, rewardStr.length - 2);

  var rewards = rewardStr.split(this.SPLIT_SELECT);
  var ratios = [];

  var itemStr;
  var itemRatioStr;
  var seperateIndex;
  for (var i = 0; i < rewards.length; i++) {
    itemStr = rewards[i];
    seperateIndex = itemStr.lastIndexOf(this.SPLIT_PROPER);
    itemRatioStr = itemStr.substr(seperateIndex + 1);
    rewards[i] = itemStr.substring(0, seperateIndex);

    ratios.push(parseInt(itemRatioStr));
  }

  var stairs = [];
  var sum = 0;
  for (var i = 0; i < ratios.length; i++) {
    sum = sum + ratios[i];
    stairs.push(sum);
  }

  var resultIndex;
  var mrandom = (Math.random() * sum);
  for (var i = 0; i < stairs.length; i++) {
    if (mrandom < stairs[i]) {
      resultIndex = i;
      break;
    }
  }

  return rewards[resultIndex];
}

/**
 * 过滤空格
 * */
pro.filterSpace = function (rewardStr) {
  rewardStr = rewardStr.replace(/ /g, '');
  return rewardStr;
};

/**
 * 合并奖励的数量
 * 如果调用过 setCountName 设置过数量属性,可以把其他属性都一样的奖励合并到一起
 * @param rewards 奖励数组
 */
pro.mergeCount = function (rewards) {
  // 1. 找到所有包含 countName 的奖励,
  // 2. 然后检查其他属性是否一样,一样则合并到出现的第一个

  if (!this.countName) {
    return;
  }

  var propStrs = [];

  var propStr;
  var item;
  var index;

  for (var i = 0; i < rewards.length; i++) {
    item = rewards[i];

    propStr = this.getPropStrNoCount(item);

    index = propStrs.indexOf(propStr);

    if (index === -1) {
      propStrs.push(propStr);
    } else {

      if (item.hasOwnProperty(this.countName)) {
        rewards.splice(i, 1);
        rewards[index][this.countName] += item[this.countName];
        i--;
      }
    }
  }
};

/**
 * 得到奖励的属性字符串
 */
pro.getPropStrNoCount = function (item) {

  var keys = this.types[item.type].args.concat();

  var indexOfCount = -1;

  while (indexOfCount = keys.indexOf(this.countName), indexOfCount >= 0) {
    keys.splice(indexOfCount, 1);
  }

  keys.push('type');
  keys.sort();

  var arr = [];
  keys.forEach(function (name) {
    arr.push(item[name]);
  });

  return arr.join(',');
};

/**
 * 解析,分隔的字符串
 * */
pro.parseReward = function (rewardStr) {
  rewardStr = rewardStr || '';
  rewardStr = this.filterSpace(rewardStr);
  var result = [];
  var rewards = rewardStr.split(this.SPLIT_REWARD);

  var reward;
  var itemStr;
  for (var i = 0; i < rewards.length; i++) {
    itemStr = rewards[i];

    itemStr = this.filterSelect(itemStr);
    reward = this.parseRewardBase(itemStr);
    reward && result.push(reward);
  }

  if (this.countName) {
    this.mergeCount(result);
  }

  return result;

};

/**
 * 奖励转换成字符串
 * @param rewards 奖励数组
 * @return {String}
 */
pro.rewardToStr = function (rewards) {
  var result = [];
  for (var i = 0; i < rewards.length; i++) {
    result.push(this.rewardToStrBase(rewards[i]));
  }

  return result.join(this.SPLIT_REWARD);
};

/**
 * 单个奖励转字符串
 * @param reward
 */
pro.rewardToStrBase = function (reward) {
  var keyArr = ['type'];
  keyArr = keyArr.concat(this.types[reward.type].args);
  var propArr = [];

  keyArr.forEach(function (name) {
    propArr.push(reward[name]);
  })

  return propArr.join(this.SPLIT_PROPER);
};

/**
 * 真正加到玩家身上
 * @param rewardStr 奖励字符串
 * @param oneCallback 一个obj的处理函数
 * */
pro.addReward = function (rewardStr, oneCallback, args) {
  var rewards = this.parseReward(rewardStr);

  var args = Array.prototype.splice.call(arguments, 2);

  for (var i = 0; i < rewards.length; i++) {

    if (oneCallback) {
      var margs = args.concat();
      margs.unshift(rewards[i]);
      oneCallback.apply(null, margs);
    }
  }
  return rewards;
}

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


<commit_before>function getCalendarDateHours(date, timeHours) {
    var date = new moment(date);
    date.hours(timeHours);
    return date;
}
function getCalendarDateTime(date, timeHours, timeMinutes) {
    var dateWithHours = getCalendarDateHours(date, timeHours);
    var date = new moment(dateWithHours);
    date.minutes(timeMinutes);
    return date;
}
Template.assignmentCalendar.helpers({
    assignmentType: function () {
        return CurrentAssignmentType.get();
    },
    days: function () {
        return CalendarDays.find({});
    },
    hours: function () {
        return CalendarHours.find({});
    },
    quarter: function () {
        return CalendarQuarter.find({});
    },
    displayCalendarTitleDate: function (date) {
        return new moment(date).format("dddd DD/MM");
    },
    hoursDate: function (date) {
        return getCalendarDateHours(date, this.date);
    },
    quarterDate: function (date, timeHours) {
        return getCalendarDateTime(date, timeHours, this.quarter);
    },

    labelSkills: function () {
        return Skills.findOne({_id: this.toString()}).label;
    },

    userName: function () {
        return Users.findOne({_id: this.userId}).name;
    },

    teamName: function () {
        return Teams.findOne({_id: this.teamId}).name;
    },

    timeSlot: function (date, timeHours, idTask) {
        var startCalendarTimeSlot = getCalendarDateTime(date, timeHours);
        var currentAssignmentType = CurrentAssignmentType.get();

        var data = {};

        switch (currentAssignmentType) {
            case AssignmentType.USERTOTASK:
                var user = SelectedUser.get() == null ? null : Users.findOne(SelectedUser.get());
                if (user === null) return [];

                var availabilityFound = AvailabilityService.getAvailabilityByStart(user.availabilities, startCalendarTimeSlot);
                var assignmentFound = AssignmentService.getAssignmentByStart(user.assignments, startCalendarTimeSlot);

                if (availabilityFound === null && assignmentFound === null) return [];
                if (availabilityFound !== null && assignmentFound !== null) {
                    console.error("Calendar.timeSlot : error while displaying user info, both availability and assignment has been found. \nuser", user, " => availability", availabilityFound, " and assignment", assignmentFound);
                    return [];
                }

                var baseOneHourHeight = 40;
                var accuracy = CalendarAccuracy.findOne().accuracy;

                var data = {}, founded;

                if (availabilityFound !== null) {
                    data.state = "available";
                    data.name = user.name;

                    founded = availabilityFound;
                } else if (assignmentFound !== null) {
                    data.name = assignmentFound.taskName;
                    data.state = "affecte";

                    founded = assignmentFound;
                }

                _.extend(data, founded);
                var end = new moment(founded.end);
                var start = new moment(founded.start);
                var duration = end.diff(start) / (3600 * 1000);

                var height = accuracy * baseOneHourHeight * duration;
                data.height = height + "px";

                break;
            case AssignmentType.TASKTOUSER:
                var task = SelectedTask.get() == null ? null : Tasks.findOne(SelectedTask.get());
                if (task === null) return [];

                var timeSlotFound = TimeSlotService.getTimeSlotByStart(task.timeSlots, startCalendarTimeSlot);
                var assignmentsFound = AssignmentService.getAssignmentByStart(task.assignments, startCalendarTimeSlot, true);

                if (timeSlotFound === null && assignmentsFound.length === 0) return [];

                var baseOneHourHeight = 40;
                var accuracy = CalendarAccuracy.findOne().accuracy;

                var data = {}, founded;

                if (timeSlotFound !== null) {
                    data.state = "available";
                    //data.name = task.name;
                    //Template.parentData() doesn't work so we use a trick
                    data.taskId = task._id;

                    founded = timeSlotFound;

                    //people need
                    var peopleNeeds = founded.peopleNeeded;
                    data.peopleNeeded = peopleNeeds;

                }

                //if (assignmentsFound.length !== 0) { //at least one assignment TODO code couleur d'avancement en fonction des peoples needed
                //    data.name = assignmentsFound[0].taskName; //idem, la meme task
                //    data.state = "in-progress";
                //    data.taskId = task._id;
                //
                //
                //    founded = assignmentsFound[0]; //normalement ils ont tous les memes date, TODO controler ca
                //}

                _.extend(data, founded);
                var end = new moment(founded.end);
                var start = new moment(founded.start);
                var duration = end.diff(start) / (3600 * 1000);

                var height = accuracy * baseOneHourHeight * duration;
                data.height = height + "px";

                break;
            case AssignmentType.ALL:
                return [];
        }

        return [data];  //le css ne sait pas encore gerer deux data timeSlot sur un meme calendar timeSlot
    },
    sideHoursHeight: function () {
        switch (CalendarAccuracy.findOne({}).accuracy) {
            case 0.25 :
                return "oneHour";
            case  0.5 :
                return "oneHour";
            case 1:
                return "oneHour";
            case  2:
                return "twoHour"
            case 4:
                return "fourHour"
        }
    },
    quarterHeight: function () {
        switch (CalendarAccuracy.findOne({}).accuracy) {
            case 0.25 :
                return "quarterHour";
            case  0.5 :
                return "halfHour";
            case 1:
                return "oneHour";
            case  2:
                return "twoHour"
            case 4:
                return "fourHour"
        }
    }

});

selectedPeopleNeed = null;

Template.assignmentCalendar.events({
    "click .peopleNeed": function (event) {
        selectedPeopleNeed = this;

        //event should bubbles to .creneau
    },

    //taskToUser (we click on a complete task time slot)
    "click .creneau": function (event) {
        //TODO gerer le double click pour la desaffectation

        var currentAssignmentType = CurrentAssignmentType.get();

        switch (currentAssignmentType) {
            case AssignmentType.USERTOTASK:
                console.error("Template.assignmentCalendar.events.click .creneau", "User can't normally click on this kind of element when in userToTask");
                return;
                break;
            case AssignmentType.TASKTOUSER: //only display users that have at least one availability matching the selected time slot
                var selectedTimeSlot = this;
                selectedTimeslotId = selectedTimeSlot._id;

                //Template.parentData() doesn't work so we use a trick to retrieve taskId
                var task = Tasks.findOne({_id: selectedTimeSlot.taskId});
                var timeSlot = TimeSlotService.getTimeSlot(task, selectedTimeSlot._id);

                /**
                 *
                 * By now, userId, teamId and skills can't be combined.
                 * In particular we can't ask for a specific team and for specific skills (will be soon)
                 *
                 * Skills filter
                 *
                 * For selected task's time slot, the user must have all the required skills of at least
                 * one of task's people need
                 *
                 */
                var peopleNeeded = selectedPeopleNeed;
                var askingSpecificNeedAndSkills = [];
                if (peopleNeeded.userId) { //prior above teamId an skills
                    askingSpecificNeedAndSkills.push({
                        _id: peopleNeeded.userId
                    });
                } else if (peopleNeeded.teamId && peopleNeeded.skills.length !== 0) {  //we combine teamId and skills
                    askingSpecificNeedAndSkills.push({
                        $and: [
                            {
                                teams: peopleNeeded.teamId
                            },
                            {
                                skills: {
                                    $all: peopleNeeded.skills
                                }
                            }
                        ]
                    });
                } else if (peopleNeeded.teamId) { //we only use teamId
                    askingSpecificNeedAndSkills.push({
                        teams: peopleNeeded.teamId
                    });
                } else if (peopleNeeded.skills.length !== 0) //if people need doesn't require any particular skills
                    askingSpecificNeedAndSkills.push({skills: {$all: peopleNeeded.skills}});

                var userTeamsSkillsFilter;
                if (askingSpecificNeedAndSkills.length !== 0) //if all time slot's people need don't require any particular skills
                    userTeamsSkillsFilter = {
                        $or: askingSpecificNeedAndSkills
                    };

                var availabilitiesFilter = {
                    availabilities: {
                        $elemMatch: {
                            start: {$lte: timeSlot.start},
                            end: {$gte: timeSlot.end}
                        }
                    }
                };

                /**
                 * The user must be free during the time slot duration and have skills that match the required ones
                 */
                var newFilter = {
                    $and: [
                        availabilitiesFilter,
                        userTeamsSkillsFilter
                    ]
                };
                console.info("TASKTOUSER user filter", newFilter);

                UserFilter.set(newFilter);
                break;
        }
    },

    //userToTask (we click on a creneau, not on the entire availability)
    "click .heure, .quart_heure": function (event) {
        //TODO gerer le double click pour la desaffectation

        var currentAssignmentType = CurrentAssignmentType.get();

        switch (currentAssignmentType) {
            case AssignmentType.USERTOTASK://only display task that have at least one time slot matching the selected availability slot

                //what time did we click on ?
                var $target = $(event.target);

                var selectedDate = null;
                if (typeof $target.attr("hours") !== "undefined") {
                    selectedDate = new moment(new Date($target.attr("hours")));
                } else if (typeof $target.attr("quarter") !== "undefined") {
                    selectedDate = new moment(new Date($target.attr("quarter")));
                }
                selectedDateUserToTask = selectedDate;

                var userId = SelectedUser.get()._id;
                var user = Users.findOne({_id: userId});
                var availability = AvailabilityService.getSurroundingAvailability(user, selectedDate);

                if (typeof availability === "undefined") {
                    console.error("Template.assignmentCalendar.events.click .heure, .quart_heure", "User can't normally click on this kind of element when in userToTask");
                    return;
                }
                selectedAvailability = availability;

                /*

                 ** Skills filter
                 User is eligible for a task if he has all skills for at least one task' people need's skills.
                 The query looks like something like this : 'foreach timeSlot foreach peopleNeeded foreach skills' = at least user.skills

                 ** Availabilities filter :
                 Task whose have at least one timeSlot (to begin, just one) as
                 user.selectedAvailabilities.start <= task.timeslot.start <= selectedDate and
                 selectedDate <=  task.timeslot.end <=  user.Dispocorrespante.end

                 Foreach task's time slot, we need a matching skills and a matching availability

                 */

                var newFilter = {
                    $or: [ //$or does't work on $elemMatch with miniMongo, so we use it here
                        { //userId filter
                            timeSlots: {
                                $elemMatch: {
                                    //skills filter
                                    peopleNeeded: {
                                        $elemMatch: {
                                            userId: user._id
                                        }
                                    }
                                },
                                //availabilities filter
                                // start: {$gte: availability.start, $lte: selectedDate.toDate()},
                                // end: {$gt: selectedDate.toDate(), $lte: availability.end}
                            }
                        },
                        {
                            $or: [ //either we match skills requirement or there is no skills requirement (and we don't care)
                                { //skills filter
                                    timeSlots: {
                                        $elemMatch: {
                                            //skills filter
                                            peopleNeeded: {
                                                $elemMatch: {
                                                    skills: user.skills,
                                                    teamId: {
                                                        $in: user.teams
                                                    }
                                                    //{  ////=> or just skills : user.skills (what the differences ?)
                                                    //    $elemMatch: {
                                                    //        $in: user.skills
                                                    //    }
                                                    //}
                                                }
                                            },
                                            //availabilities filter
                                            start: {$gte: availability.start, $lte: selectedDate.toDate()},
                                            end: {$gt: selectedDate.toDate(), $lte: availability.end}
                                        }
                                    }
                                },
                                {//no-skills filter
                                    timeSlots: {
                                        $elemMatch: {
                                            //skills filter
                                            peopleNeeded: {
                                                $elemMatch: {
                                                    skills: { // $eq : [] doesn't work with miniMongo, here is a trick
                                                        $not: {
                                                            $ne: []
                                                        }
                                                    },
                                                    teamId: {
                                                        $in: user.teams
                                                    }
                                                }
                                            },
                                            //availabilities filter
                                            start: {$gte: availability.start, $lte: selectedDate.toDate()},
                                            end: {$gt: selectedDate.toDate(), $lte: availability.end}
                                        }
                                    }
                                }
                            ]
                        }
                    ]
                };
                //aggregate is not supported by mini mongo

                TaskFilter.set(newFilter);
                break;
            case
            AssignmentType.TASKTOUSER:
                //only display users that have at least one availability matching the selected time slot
                //we let the event bubbles to the parent
                return [];
        }
    }
});

<commit_msg>[RPI] prepare remove assingment mode taskToUser<commit_after>function getCalendarDateHours(date, timeHours) {
    var date = new moment(date);
    date.hours(timeHours);
    return date;
}
function getCalendarDateTime(date, timeHours, timeMinutes) {
    var dateWithHours = getCalendarDateHours(date, timeHours);
    var date = new moment(dateWithHours);
    date.minutes(timeMinutes);
    return date;
}
Template.assignmentCalendar.helpers({
    assignmentType: function () {
        return CurrentAssignmentType.get();
    },
    days: function () {
        return CalendarDays.find({});
    },
    hours: function () {
        return CalendarHours.find({});
    },
    quarter: function () {
        return CalendarQuarter.find({});
    },
    displayCalendarTitleDate: function (date) {
        return new moment(date).format("dddd DD/MM");
    },
    hoursDate: function (date) {
        return getCalendarDateHours(date, this.date);
    },
    quarterDate: function (date, timeHours) {
        return getCalendarDateTime(date, timeHours, this.quarter);
    },

    labelSkills: function () {
        return Skills.findOne({_id: this.toString()}).label;
    },

    userName: function () {
        return Users.findOne({_id: this.userId}).name;
    },

    teamName: function () {
        return Teams.findOne({_id: this.teamId}).name;
    },

    timeSlot: function (date, timeHours, idTask) {
        var startCalendarTimeSlot = getCalendarDateTime(date, timeHours);
        var currentAssignmentType = CurrentAssignmentType.get();

        var data = {};

        switch (currentAssignmentType) {
            case AssignmentType.USERTOTASK:
                var user = SelectedUser.get() == null ? null : Users.findOne(SelectedUser.get());
                if (user === null) return [];

                var availabilityFound = AvailabilityService.getAvailabilityByStart(user.availabilities, startCalendarTimeSlot);
                var assignmentFound = AssignmentService.getAssignmentByStart(user.assignments, startCalendarTimeSlot);

                if (availabilityFound === null && assignmentFound === null) return [];
                if (availabilityFound !== null && assignmentFound !== null) {
                    console.error("Calendar.timeSlot : error while displaying user info, both availability and assignment has been found. \nuser", user, " => availability", availabilityFound, " and assignment", assignmentFound);
                    return [];
                }

                var baseOneHourHeight = 40;
                var accuracy = CalendarAccuracy.findOne().accuracy;

                var data = {}, founded;

                if (availabilityFound !== null) {
                    data.state = "available";
                    data.name = user.name;

                    founded = availabilityFound;
                } else if (assignmentFound !== null) {
                    data.name = assignmentFound.taskName;
                    data.state = "affecte";

                    founded = assignmentFound;
                }

                _.extend(data, founded);
                var end = new moment(founded.end);
                var start = new moment(founded.start);
                var duration = end.diff(start) / (3600 * 1000);

                var height = accuracy * baseOneHourHeight * duration;
                data.height = height + "px";

                break;
            case AssignmentType.TASKTOUSER:
                var task = SelectedTask.get() == null ? null : Tasks.findOne(SelectedTask.get());
                if (task === null) return [];

                var timeSlotFound = TimeSlotService.getTimeSlotByStart(task.timeSlots, startCalendarTimeSlot);
                var assignmentsFound = AssignmentService.getAssignmentByStart(task.assignments, startCalendarTimeSlot, true);

                if (timeSlotFound === null && assignmentsFound.length === 0) return [];

                var baseOneHourHeight = 40;
                var accuracy = CalendarAccuracy.findOne().accuracy;

                var data = {}, founded;

                if (timeSlotFound !== null) {
                    data.state = "available";
                    //data.name = task.name;
                    //Template.parentData() doesn't work so we use a trick
                    data.taskId = task._id;

                    founded = timeSlotFound;

                    //people need
                    var peopleNeeds = founded.peopleNeeded;
                    data.peopleNeeded = peopleNeeds;

                }

                //if (assignmentsFound.length !== 0) { //at least one assignment TODO code couleur d'avancement en fonction des peoples needed
                //    data.name = assignmentsFound[0].taskName; //idem, la meme task
                //    data.state = "in-progress";
                //    data.taskId = task._id;
                //
                //
                //    founded = assignmentsFound[0]; //normalement ils ont tous les memes date, TODO controler ca
                //}

                _.extend(data, founded);
                var end = new moment(founded.end);
                var start = new moment(founded.start);
                var duration = end.diff(start) / (3600 * 1000);

                var height = accuracy * baseOneHourHeight * duration;
                data.height = height + "px";

                break;
            case AssignmentType.ALL:
                return [];
        }

        return [data];  //le css ne sait pas encore gerer deux data timeSlot sur un meme calendar timeSlot
    },
    sideHoursHeight: function () {
        switch (CalendarAccuracy.findOne({}).accuracy) {
            case 0.25 :
                return "oneHour";
            case  0.5 :
                return "oneHour";
            case 1:
                return "oneHour";
            case  2:
                return "twoHour"
            case 4:
                return "fourHour"
        }
    },
    quarterHeight: function () {
        switch (CalendarAccuracy.findOne({}).accuracy) {
            case 0.25 :
                return "quarterHour";
            case  0.5 :
                return "halfHour";
            case 1:
                return "oneHour";
            case  2:
                return "twoHour"
            case 4:
                return "fourHour"
        }
    }

});

selectedPeopleNeed = null;

var peopleNeedAssignedClick = 0;

Template.assignmentCalendar.events({
    "click .peopleNeed:not(.assigned)": function (event) {
        selectedPeopleNeed = this;

        //event should bubbles to .creneau
    },

    "click .peopleNeed.assigned": function (event) {
        event.stopPropagation();
        peopleNeedAssignedClick++;
        if (peopleNeedAssignedClick == 1) {
            setTimeout(function () {
                if (peopleNeedAssignedClick == 1) {
                    console.info("click on peopleNeed.assigned : double click to perform remove assignment");
                } else {
                        console.info("dblclick on peopleNeed.assigned : TODO remove assignment");
                    //    //TODO remove assignment

                }
                peopleNeedAssignedClick = 0;
            },  300);
        }

    },
    //
    //"dblclick .peopleNeed.assigned": function (event) {
    //    console.info("dblclick on peopleNeed.assigned : TODO remove assignment");
    //    //TODO remove assignment
    //},

    //taskToUser (we click on a complete task time slot)
    "click .creneau": function (event) {
        //TODO gerer le double click pour la desaffectation

        var currentAssignmentType = CurrentAssignmentType.get();

        switch (currentAssignmentType) {
            case AssignmentType.USERTOTASK:
                console.error("Template.assignmentCalendar.events.click .creneau", "User can't normally click on this kind of element when in userToTask");
                return;
                break;
            case AssignmentType.TASKTOUSER: //only display users that have at least one availability matching the selected time slot
                var selectedTimeSlot = this;
                selectedTimeslotId = selectedTimeSlot._id;

                //Template.parentData() doesn't work so we use a trick to retrieve taskId
                var task = Tasks.findOne({_id: selectedTimeSlot.taskId});
                var timeSlot = TimeSlotService.getTimeSlot(task, selectedTimeSlot._id);

                /**
                 *
                 * By now, userId, teamId and skills can't be combined.
                 * In particular we can't ask for a specific team and for specific skills (will be soon)
                 *
                 * Skills filter
                 *
                 * For selected task's time slot, the user must have all the required skills of at least
                 * one of task's people need
                 *
                 */
                var peopleNeeded = selectedPeopleNeed;
                var askingSpecificNeedAndSkills = [];
                if (peopleNeeded.userId) { //prior above teamId an skills
                    askingSpecificNeedAndSkills.push({
                        _id: peopleNeeded.userId
                    });
                } else if (peopleNeeded.teamId && peopleNeeded.skills.length !== 0) {  //we combine teamId and skills
                    askingSpecificNeedAndSkills.push({
                        $and: [
                            {
                                teams: peopleNeeded.teamId
                            },
                            {
                                skills: {
                                    $all: peopleNeeded.skills
                                }
                            }
                        ]
                    });
                } else if (peopleNeeded.teamId) { //we only use teamId
                    askingSpecificNeedAndSkills.push({
                        teams: peopleNeeded.teamId
                    });
                } else if (peopleNeeded.skills.length !== 0) //if people need doesn't require any particular skills
                    askingSpecificNeedAndSkills.push({skills: {$all: peopleNeeded.skills}});

                var userTeamsSkillsFilter;
                if (askingSpecificNeedAndSkills.length !== 0) //if all time slot's people need don't require any particular skills
                    userTeamsSkillsFilter = {
                        $or: askingSpecificNeedAndSkills
                    };

                var availabilitiesFilter = {
                    availabilities: {
                        $elemMatch: {
                            start: {$lte: timeSlot.start},
                            end: {$gte: timeSlot.end}
                        }
                    }
                };

                /**
                 * The user must be free during the time slot duration and have skills that match the required ones
                 */
                var newFilter = {
                    $and: [
                        availabilitiesFilter,
                        userTeamsSkillsFilter
                    ]
                };
                console.info("TASKTOUSER user filter", newFilter);

                UserFilter.set(newFilter);
                break;
        }
    },

    //userToTask (we click on a creneau, not on the entire availability)
    "click .heure, .quart_heure": function (event) {
        //TODO gerer le double click pour la desaffectation

        var currentAssignmentType = CurrentAssignmentType.get();

        switch (currentAssignmentType) {
            case AssignmentType.USERTOTASK://only display task that have at least one time slot matching the selected availability slot

                //what time did we click on ?
                var $target = $(event.target);

                var selectedDate = null;
                if (typeof $target.attr("hours") !== "undefined") {
                    selectedDate = new moment(new Date($target.attr("hours")));
                } else if (typeof $target.attr("quarter") !== "undefined") {
                    selectedDate = new moment(new Date($target.attr("quarter")));
                }
                selectedDateUserToTask = selectedDate;

                var userId = SelectedUser.get()._id;
                var user = Users.findOne({_id: userId});
                var availability = AvailabilityService.getSurroundingAvailability(user, selectedDate);

                if (typeof availability === "undefined") {
                    console.error("Template.assignmentCalendar.events.click .heure, .quart_heure", "User can't normally click on this kind of element when in userToTask");
                    return;
                }
                selectedAvailability = availability;

                /*

                 ** Skills filter
                 User is eligible for a task if he has all skills for at least one task' people need's skills.
                 The query looks like something like this : 'foreach timeSlot foreach peopleNeeded foreach skills' = at least user.skills

                 ** Availabilities filter :
                 Task whose have at least one timeSlot (to begin, just one) as
                 user.selectedAvailabilities.start <= task.timeslot.start <= selectedDate and
                 selectedDate <=  task.timeslot.end <=  user.Dispocorrespante.end

                 Foreach task's time slot, we need a matching skills and a matching availability

                 */

                var newFilter = {
                    $or: [ //$or does't work on $elemMatch with miniMongo, so we use it here
                        { //userId filter
                            timeSlots: {
                                $elemMatch: {
                                    //skills filter
                                    peopleNeeded: {
                                        $elemMatch: {
                                            userId: user._id
                                        }
                                    }
                                },
                                //availabilities filter
                                // start: {$gte: availability.start, $lte: selectedDate.toDate()},
                                // end: {$gt: selectedDate.toDate(), $lte: availability.end}
                            }
                        },
                        {
                            $or: [ //either we match skills requirement or there is no skills requirement (and we don't care)
                                { //skills filter
                                    timeSlots: {
                                        $elemMatch: {
                                            //skills filter
                                            peopleNeeded: {
                                                $elemMatch: {
                                                    skills: user.skills,
                                                    teamId: {
                                                        $in: user.teams
                                                    }
                                                    //{  ////=> or just skills : user.skills (what the differences ?)
                                                    //    $elemMatch: {
                                                    //        $in: user.skills
                                                    //    }
                                                    //}
                                                }
                                            },
                                            //availabilities filter
                                            start: {$gte: availability.start, $lte: selectedDate.toDate()},
                                            end: {$gt: selectedDate.toDate(), $lte: availability.end}
                                        }
                                    }
                                },
                                {//no-skills filter
                                    timeSlots: {
                                        $elemMatch: {
                                            //skills filter
                                            peopleNeeded: {
                                                $elemMatch: {
                                                    skills: { // $eq : [] doesn't work with miniMongo, here is a trick
                                                        $not: {
                                                            $ne: []
                                                        }
                                                    },
                                                    teamId: {
                                                        $in: user.teams
                                                    }
                                                }
                                            },
                                            //availabilities filter
                                            start: {$gte: availability.start, $lte: selectedDate.toDate()},
                                            end: {$gt: selectedDate.toDate(), $lte: availability.end}
                                        }
                                    }
                                }
                            ]
                        }
                    ]
                };
                //aggregate is not supported by mini mongo

                TaskFilter.set(newFilter);
                break;
            case
            AssignmentType.TASKTOUSER:
                //only display users that have at least one availability matching the selected time slot
                //we let the event bubbles to the parent
                return [];
        }
    }
});

<|endoftext|>