<commit_before>var express       = require('express'),
    Error404      = require('../errors').Error404,
    path          = require('path'),
    mkdirp        = require('mkdirp'),
    fs            = require('fs'),
    requireUser   = require('../middleware/route').requireUser,
    _             = require('underscore'),
    async         = require('async'),
    csv           = require('csv');

var log           = console.log;

module.exports = function () {

  var app = express.createServer();

  app.schemas = {'person': {
      name: 'Person',
      model: 'Person',
      options: {
        summary: {
          title: 'Summary',
          strict: true,
          suggestions: ['Summary']
        },
        name: {
          title: 'Name, birthday & similar',
          strict: true,
          suggestions: ['Full name', 'Title', 'First name', 'Last name', 'Middle name', 'Birthday', 'Nickname']
        },
        position: {
          title: 'Position'
        },
        contact: {
          title: 'Contact Information',
          strict: false,
          suggestions: ['Address', 'Phone', 'Fax', 'Email', 'Skype']
        },
        links: {
          title: 'Link',
          strict: false,
          suggestions: ['Website', 'Wikipedia', 'Youtube', 'Twitter', 'Freebase' ,'RSS']
        },
        id: {
          title: 'ID',
          strict: false,
          suggestions: ['Facebook', 'Twitter']
        },
        data: {
          title: 'Other data',
          strict: false,
          suggestions: ['University', 'School', 'Gender']
        }
      }
  }};

  /*
   * Parses a csv from path and calls the callback with the parsed data.
   */
  app.parseCsv = function(path, callback) {
    var parsedData = {};

    csv()
      .fromPath(path)
      .on('data',function(data, index){
        parsedData[index] = data;
        //log('#'+index+' '+JSON.stringify(data));
      })
    .on('end',function(count){
      //log('Number of lines: '+count);
      callback(parsedData, null);
    })
    .on('error',function(err){
      console.log(err.message);
      callback(parsedData, err);
    });
  }

  /*
   * Converts mapping+attributes into easily accessible dict.
   * Take a look at the tests for an example.
   */
  app.zipIt = function(mappings, attributes) {
    var inverted = {};
    _.each(_.zip(mappings, attributes), function(item) {
      if (!item[0][0] || !item[0][1] || !item[0][2]) {
        return;
      }
      if (!inverted[item[0][1]]) {
        inverted[item[0][1]] = {};
      }
      inverted[item[0][1]][item[0][2]] = item[1];
    });

    return inverted;
  }

  /*
   * Writes the data from a form into the database according to the provided mapping.
   */
  app.doImport = function(popit, schema, mappings, data, updateCb, cb) {
    /*
    log('schema:', schema);
    log('mappings:', mappings);
    log('popit:', popit);
    log('data:', data);
    //*/

    var Person = popit.model('Person'),
      Link = popit.model('Link'),
      Position = popit.model('Position'),
      Organisation = popit.model('Organisation'),
      ContactDetail = popit.model('ContactDetail'),
      positions = [], people = [], organisations = [];

    // async.forEach does not support objects, data is an object, not an array
    async.forEach(Object.keys(data), function(key, callback) {
      var person, useable, res = [], attributes, name = '';

      attributes = data[key];

      useable = app.zipIt(mappings, attributes);

      if (useable.name['Title']) {
        name += useable.name['Title']+' ';
      }

      // either full or first name
      if (useable.name['First name']) {
        name += useable.name['First name'];
      } else {
        name += useable.name['Full name'];
      }

      if (useable.name['Middle name']) {
        name += ' '+ useable.name['Middle name'];
      }
      if (useable.name['Last name']) {
        name += ' '+ useable.name['Last name'];
      }

      // sometimes there are multiline entries in csv's
      if (name == '') {
        person = people.pop()
      } else {
        // create new person
        person = new Person({'name': name, 'contact_details' : [], 'data': {}, 'links': []}, false);
        person.set("ids", []);
      }

      if (useable.name["Birthdate"])
        person.set('birthDate', useable.name["Birthdate"]);

      async.parallel([
          function(callback) {
            _.each(useable['summary'], function(value, key) {
              if (value) {
                person.set('summary', value);
              }
            });
            callback();
          },
          function(callback) {
            _.each(useable['contact'], function(value, key) {
              if (value) {
                var cd = new ContactDetail({kind: key, value: value});
                person.contact_details.push(cd);
              }
            });
            callback();
          },
          function(callback) {
            _.each(useable['links'], function(value, key) {
              if (value) {
                var l = new Link({comment: key, url: value});
                person.links.push(l);
              }
            });
            callback();
          },
          function(callback) {
            // simple key value/ key array pairs
            //var s = person.get("data") || {}
            var s = person.data
            _.each(useable['data'], function(value, key) {
              if (value) {
                if (s[key]) {
                  s[key].push(value)
                } else {
                  s[key] = [value];
                }
              }
            });
            //person.set('data', s);
            callback();
          },
          function(callback) {
            // ids may get a little bit more information in the future
            var ids = person.get("ids") || {}
            _.each(useable['id'], function(value, key) {
              var i = {provider: key, id: value};
              ids.push(i);
              });
            person.set('ids', ids);
            callback();
          },
          function(callback) {
            if (useable['position']) {
              async.forEach(Object.keys(useable['position']), function(key, callback) {
                var value = useable['position'][key];
                if (value) {
                  Organisation.findOne({name: value}, function(err, doc) {
                    if (err) console.log( err );
                    else {
                      // find organisation in array to avoid problems when saving
                      doc = doc || _.find(organisations, function(e) {return e.name == value});
                      if (!doc) {
                        doc = new Organisation({name: value});
                        organisations.push(doc);
                      }
                      var pos = new Position({
                        title: value,
                          organisation: doc,
                          person: person,
                      });
                      positions.push(pos);
                    }
                  callback(err);
                  });
                }
              }, function(err) {
                callback(err);
              });
            } else {
              callback();
            }
          }],

          function(err, results) {

            /*
            log('useable:', useable);
            log('positions:', positions);
            log('organisation:', organisations);
            log('person:', person);
            //*/

            people.push(person);

            callback(err); //forEach callback
          }); // end of parallel

    }, function(err) {
      if (err) {
        cb(err);
      }

      //log(people)

      var toBeSaved = _.union(people, positions, organisations);
      var total = toBeSaved.length;
      var progress = 0;

      // save people and positions to database
      // TODO make parallel, take care of slugs
      async.forEachSeries(toBeSaved, function(item, callback) {
        item.save(function (err) {
          progress++;
          updateCb(progress, total);
          callback(err);
        });
      }, function(err) {
        cb(err, people, positions, organisations);
      });

    });
  }

  app.mounted(function(parent) {

    var app = this;

    /*
     * Shows the migration/ index page
     */
    var showMigrationPage = function(req, res) {
      var query = req.popit.model('Migration').find().sort('created', 1);

      query.exec(function(err, docs) {
        if (err) throw err;

        res.local('migration', docs);
        res.render('migration');
      });
    }

    /*
     * Shows the status page while the migration is running
     */
    var showStatusPage = function(req, res) {
      res.render('migration/status');
    }

    /*
     * Show mapping page for a migration
     */
    var showMappingPage = function(req, res) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        //log(doc, req.params.id);
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          res.local('migration', doc);
          res.local('schemas', app.schemas);
          res.local('_', _);
          res.render('migration/mapping');
        }
      });
    }

    /*
     * Shows the migration/ index page
     */
    app.get('/', requireUser, function (req,res) {
      if( ! res.local('error') ) res.local('error', '');

      showMigrationPage(req, res);
    });

    /*
     * Deletes all migrations from the database.
     * This does not mean the imported data is lost, it just
     * means that the migration object is deleted.
     */
    app.get('/clear', requireUser, function (req,res) {
      var query = req.popit.model('Migration').find();

      query.exec(function(err, docs) {
        if (err) throw err;

        docs.forEach( function (doc) {
          doc.remove(function(err) {
            if (err) throw err;
          });
        });

        res.redirect('/migration');
      });
    });

    /*
     * Creates a new migration object
     */
    var saveMigration = function(req, file, parsedData, callback) {
      var migration, header, data;

      header = parsedData[0];
      delete parsedData[0];
      data = parsedData;

      migration = new (req.popit.model('Migration'))({
        source: {
          name: file.name,
          mime_type: file.type,
          parsed: {
            header: header,
            data: data
          }
        },
        tracking: {
          progress: 0,
          total: 0,
          count: 0,
          err: []
        }
      });

      migration.save(function(err, obj) {
        callback(obj);
      });
    }

    /*
     * Handle upload and parse provided csv
     */
    var uploadPost = function(req, res, next) {
      //log(req.files);

      if ( !req.files || req.files.source.size == 0) {
        log("no file found");
        res.local( 'error', 'required');
        res.redirect('/migration');
        return;
      }

      var file = req.files.source;

      app.parseCsv(file.path, function(parsedData, err) {
        // TODO handle error
        saveMigration(req, file, parsedData, function(migration) {
          req.session.migrationFile = migration.id;
          res.redirect('/migration/mapping/' + migration.id);
        });
      });
    }

    var uploadGet = function(req,res) {
      res.redirect('/migration');
    }

    /*
     * Handle the import request.
     * This happens after the CSV parsing and the definition of the mapping.
     */
    var mappingPost = function (req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          var formData = req.body;
          var mappings = _.zip(formData["csv-attribute"], formData["db-attribute-class"], formData["db-attribute"])

          var schema = formData["db-schema"];

          app.doImport(req.popit, schema, mappings, doc.source.parsed.data,
            function(progress, total) {
            // update
            //log('saved ', progress + ' of ' +  total);
            doc.tracking.progress = progress;
            doc.tracking.total = total;
            doc.save();
          }, function(err, people) {
            // finished
            doc.tracking.err = err;
            doc.tracking.count = people.length;
            doc.save();

            res.local('error', err);
            res.local('people', people);
          });

          res.redirect('/migration/status/'+req.params.id);
        }
      });
    }

    var statusGet = function(req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          showStatusPage(req, res);
        }
      });
    }

    app.get('/progress/:id', requireUser, function (req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {

          res.writeHead(200, {'Content-Type': 'application/json'});
          p = doc.tracking;
          log(p);

          res.write(JSON.stringify(p));
          res.end();
        }
      });
    });

    app.get('/status/:id', requireUser, statusGet);

    app.post('/upload', requireUser, uploadPost);
    app.get('/upload', requireUser, uploadGet);

    app.post('/mapping/:id', mappingPost);
    app.get('/mapping/:id', showMappingPage);
  });

  return app;
};
<commit_msg>Don't crash tracking in case of an error<commit_after>var express       = require('express'),
    Error404      = require('../errors').Error404,
    path          = require('path'),
    mkdirp        = require('mkdirp'),
    fs            = require('fs'),
    requireUser   = require('../middleware/route').requireUser,
    _             = require('underscore'),
    async         = require('async'),
    csv           = require('csv');

var log           = console.log;

module.exports = function () {

  var app = express.createServer();

  app.schemas = {'person': {
      name: 'Person',
      model: 'Person',
      options: {
        summary: {
          title: 'Summary',
          strict: true,
          suggestions: ['Summary']
        },
        name: {
          title: 'Name, birthday & similar',
          strict: true,
          suggestions: ['Full name', 'Title', 'First name', 'Last name', 'Middle name', 'Birthday', 'Nickname']
        },
        position: {
          title: 'Position'
        },
        contact: {
          title: 'Contact Information',
          strict: false,
          suggestions: ['Address', 'Phone', 'Fax', 'Email', 'Skype']
        },
        links: {
          title: 'Link',
          strict: false,
          suggestions: ['Website', 'Wikipedia', 'Youtube', 'Twitter', 'Freebase' ,'RSS']
        },
        id: {
          title: 'ID',
          strict: false,
          suggestions: ['Facebook', 'Twitter']
        },
        data: {
          title: 'Other data',
          strict: false,
          suggestions: ['University', 'School', 'Gender']
        }
      }
  }};

  /*
   * Parses a csv from path and calls the callback with the parsed data.
   */
  app.parseCsv = function(path, callback) {
    var parsedData = {};

    csv()
      .fromPath(path)
      .on('data',function(data, index){
        parsedData[index] = data;
        //log('#'+index+' '+JSON.stringify(data));
      })
    .on('end',function(count){
      //log('Number of lines: '+count);
      callback(parsedData, null);
    })
    .on('error',function(err){
      console.log(err.message);
      callback(parsedData, err);
    });
  }

  /*
   * Converts mapping+attributes into easily accessible dict.
   * Take a look at the tests for an example.
   */
  app.zipIt = function(mappings, attributes) {
    var inverted = {};
    _.each(_.zip(mappings, attributes), function(item) {
      if (!item[0][0] || !item[0][1] || !item[0][2]) {
        return;
      }
      if (!inverted[item[0][1]]) {
        inverted[item[0][1]] = {};
      }
      inverted[item[0][1]][item[0][2]] = item[1];
    });

    return inverted;
  }

  /*
   * Writes the data from a form into the database according to the provided mapping.
   */
  app.doImport = function(popit, schema, mappings, data, updateCb, cb) {
    /*
    log('schema:', schema);
    log('mappings:', mappings);
    log('popit:', popit);
    log('data:', data);
    //*/

    var Person = popit.model('Person'),
      Link = popit.model('Link'),
      Position = popit.model('Position'),
      Organisation = popit.model('Organisation'),
      ContactDetail = popit.model('ContactDetail'),
      positions = [], people = [], organisations = [];

    // async.forEach does not support objects, data is an object, not an array
    async.forEach(Object.keys(data), function(key, callback) {
      var person, useable, res = [], attributes, name = '';

      attributes = data[key];

      useable = app.zipIt(mappings, attributes);

      if (useable.name['Title']) {
        name += useable.name['Title']+' ';
      }

      // either full or first name
      if (useable.name['First name']) {
        name += useable.name['First name'];
      } else {
        name += useable.name['Full name'];
      }

      if (useable.name['Middle name']) {
        name += ' '+ useable.name['Middle name'];
      }
      if (useable.name['Last name']) {
        name += ' '+ useable.name['Last name'];
      }

      // sometimes there are multiline entries in csv's
      if (name == '') {
        person = people.pop()
      } else {
        // create new person
        person = new Person({'name': name, 'contact_details' : [], 'data': {}, 'links': []}, false);
        person.set("ids", []);
      }

      if (useable.name["Birthdate"])
        person.set('birthDate', useable.name["Birthdate"]);

      async.parallel([
          function(callback) {
            _.each(useable['summary'], function(value, key) {
              if (value) {
                person.set('summary', value);
              }
            });
            callback();
          },
          function(callback) {
            _.each(useable['contact'], function(value, key) {
              if (value) {
                var cd = new ContactDetail({kind: key, value: value});
                person.contact_details.push(cd);
              }
            });
            callback();
          },
          function(callback) {
            _.each(useable['links'], function(value, key) {
              if (value) {
                var l = new Link({comment: key, url: value});
                person.links.push(l);
              }
            });
            callback();
          },
          function(callback) {
            // simple key value/ key array pairs
            //var s = person.get("data") || {}
            var s = person.data
            _.each(useable['data'], function(value, key) {
              if (value) {
                if (s[key]) {
                  s[key].push(value)
                } else {
                  s[key] = [value];
                }
              }
            });
            //person.set('data', s);
            callback();
          },
          function(callback) {
            // ids may get a little bit more information in the future
            var ids = person.get("ids") || {}
            _.each(useable['id'], function(value, key) {
              var i = {provider: key, id: value};
              ids.push(i);
              });
            person.set('ids', ids);
            callback();
          },
          function(callback) {
            if (useable['position']) {
              async.forEach(Object.keys(useable['position']), function(key, callback) {
                var value = useable['position'][key];
                if (value) {
                  Organisation.findOne({name: value}, function(err, doc) {
                    if (err) console.log( err );
                    else {
                      // find organisation in array to avoid problems when saving
                      doc = doc || _.find(organisations, function(e) {return e.name == value});
                      if (!doc) {
                        doc = new Organisation({name: value});
                        organisations.push(doc);
                      }
                      var pos = new Position({
                        title: value,
                          organisation: doc,
                          person: person,
                      });
                      positions.push(pos);
                    }
                  callback(err);
                  });
                }
              }, function(err) {
                callback(err);
              });
            } else {
              callback();
            }
          }],

          function(err, results) {

            /*
            log('useable:', useable);
            log('positions:', positions);
            log('organisation:', organisations);
            log('person:', person);
            //*/

            people.push(person);

            callback(err); //forEach callback
          }); // end of parallel

    }, function(err) {
      if (err) {
        cb(err);
      }

      //log(people)

      var toBeSaved = _.union(people, positions, organisations);
      var total = toBeSaved.length;
      var progress = 0;

      // save people and positions to database
      // TODO make parallel, take care of slugs
      async.forEachSeries(toBeSaved, function(item, callback) {
        item.save(function (err) {
          progress++;
          updateCb(progress, total);
          callback(err);
        });
      }, function(err) {
        cb(err, people, positions, organisations);
      });

    });
  }

  app.mounted(function(parent) {

    var app = this;

    /*
     * Shows the migration/ index page
     */
    var showMigrationPage = function(req, res) {
      var query = req.popit.model('Migration').find().sort('created', 1);

      query.exec(function(err, docs) {
        if (err) throw err;

        res.local('migration', docs);
        res.render('migration');
      });
    }

    /*
     * Shows the status page while the migration is running
     */
    var showStatusPage = function(req, res) {
      res.render('migration/status');
    }

    /*
     * Show mapping page for a migration
     */
    var showMappingPage = function(req, res) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        //log(doc, req.params.id);
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          res.local('migration', doc);
          res.local('schemas', app.schemas);
          res.local('_', _);
          res.render('migration/mapping');
        }
      });
    }

    /*
     * Shows the migration/ index page
     */
    app.get('/', requireUser, function (req,res) {
      if( ! res.local('error') ) res.local('error', '');

      showMigrationPage(req, res);
    });

    /*
     * Deletes all migrations from the database.
     * This does not mean the imported data is lost, it just
     * means that the migration object is deleted.
     */
    app.get('/clear', requireUser, function (req,res) {
      var query = req.popit.model('Migration').find();

      query.exec(function(err, docs) {
        if (err) throw err;

        docs.forEach( function (doc) {
          doc.remove(function(err) {
            if (err) throw err;
          });
        });

        res.redirect('/migration');
      });
    });

    /*
     * Creates a new migration object
     */
    var saveMigration = function(req, file, parsedData, callback) {
      var migration, header, data;

      header = parsedData[0];
      delete parsedData[0];
      data = parsedData;

      migration = new (req.popit.model('Migration'))({
        source: {
          name: file.name,
          mime_type: file.type,
          parsed: {
            header: header,
            data: data
          }
        },
        tracking: {
          progress: 0,
          total: 0,
          count: 0,
          err: []
        }
      });

      migration.save(function(err, obj) {
        callback(obj);
      });
    }

    /*
     * Handle upload and parse provided csv
     */
    var uploadPost = function(req, res, next) {
      //log(req.files);

      if ( !req.files || req.files.source.size == 0) {
        log("no file found");
        res.local( 'error', 'required');
        res.redirect('/migration');
        return;
      }

      var file = req.files.source;

      app.parseCsv(file.path, function(parsedData, err) {
        // TODO handle error
        saveMigration(req, file, parsedData, function(migration) {
          req.session.migrationFile = migration.id;
          res.redirect('/migration/mapping/' + migration.id);
        });
      });
    }

    var uploadGet = function(req,res) {
      res.redirect('/migration');
    }

    /*
     * Handle the import request.
     * This happens after the CSV parsing and the definition of the mapping.
     */
    var mappingPost = function (req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          var formData = req.body;
          var mappings = _.zip(formData["csv-attribute"], formData["db-attribute-class"], formData["db-attribute"])

          var schema = formData["db-schema"];

          app.doImport(req.popit, schema, mappings, doc.source.parsed.data,
            function(progress, total) {
            // update
            //log('saved ', progress + ' of ' +  total);
            doc.tracking.progress = progress;
            doc.tracking.total = total;
            doc.save();
          }, function(err, people) {
            // finished
            doc.tracking.err = err;
            if (people) {
              doc.tracking.count = people.length;
            } else{
              doc.tracking.count = -1;
            };
            doc.save();

            res.local('error', err);
            res.local('people', people);
          });

          res.redirect('/migration/status/'+req.params.id);
        }
      });
    }

    var statusGet = function(req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {
          showStatusPage(req, res);
        }
      });
    }

    app.get('/progress/:id', requireUser, function (req, res, next) {
      req.popit.model('Migration').findOne({_id: req.params.id}, function(err, doc) {
        if (err) console.log( err );
        if (!doc) {
          next( new Error404() );
        } else {

          res.writeHead(200, {'Content-Type': 'application/json'});
          p = doc.tracking;
          log(p);

          res.write(JSON.stringify(p));
          res.end();
        }
      });
    });

    app.get('/status/:id', requireUser, statusGet);

    app.post('/upload', requireUser, uploadPost);
    app.get('/upload', requireUser, uploadGet);

    app.post('/mapping/:id', mappingPost);
    app.get('/mapping/:id', showMappingPage);
  });

  return app;
};
<|endoftext|>


<commit_before>var characterList = require("../data/character_list.js");

function getTypeMagnification(character, enemy) {
    if (character.type === "力" && enemy.type === "技") return 2;
    if (character.type === "技" && enemy.type === "速") return 2;
    if (character.type === "速" && enemy.type === "力") return 2;
    if (character.type === "心" && enemy.type === "知") return 2;
    if (character.type === "知" && enemy.type === "心") return 2;

    if (character.type === "技" && enemy.type === "力") return 0.5;
    if (character.type === "速" && enemy.type === "技") return 0.5;
    if (character.type === "力" && enemy.type === "速") return 0.5;

    return 1;
}

function attackAnalysis(param) {
    var attackList        = param.team;
    var boatMagnification = param.boat;
    var enemy             = param.enemy;

    function log (s) {
        if (param.log === false) return;
        console.log(s);
    }

    // get the captain
    var captainList = [];
    attackList.forEach(function (member) {
        if (member.captain === true) {
            captainList.push(characterList[member.no]);
        }
    });
    if (1 > captainList.length || captainList.length > 2) {
        log(`captain number is wrong: ${captainList.length}`);
        return null;
    }

    // show captain effect content
    log("隊長：");
    captainList.forEach(function (captain, n) {
        var name = captain.name.tw !== null ? captain.name.tw : captain.name.jp;
        var content = captain.captain.tw.content || captain.captain.jp.content;
        log(`${n + 1}. ${name} No.${captain.no}`);
        log("   " + content);
        log("");
    });
    log("--------------------------------------------------------\n");

    var chain = 1;
    var timingHistory = [];
    var total = {
        attack: 0,
        combo: 0
    };
    attackList.forEach(function(member, n) {
        var character = characterList[member.no];
        var name = character.name.tw !== null ? character.name.tw : character.name.jp;

        log(`第 ${n + 1} 位：${name}`);
        log(`原始攻擊力 = ${member.attack}, Combo = ${character.combo}`);

        var magnification = {
            boat: boatMagnification,
            type: getTypeMagnification(character, enemy),
            captain: 1,
            bead: member.bead,
            chain: chain
        };

        // get captain magnification
        captainList.forEach(function(captain) {
            var fn = captain.captain.magnification;
            magnification.captain *= fn({
                character: character,
                timingHistory: timingHistory
            });
        });

        // update timing history
        timingHistory.push(member.timing);

        log(
            `梅莉號：${magnification.boat}, `
          + `船長：${magnification.captain}, `
          + `屬珠：${magnification.bead}, `
          + `剋屬：${magnification.type}, `
          + `Chain：${magnification.chain}`
        );

        var baseAttack = member.attack
            * magnification.boat
            * magnification.type
            * magnification.captain
            * magnification.bead
            * magnification.chain;
        baseAttack = Math.round(baseAttack);
        log(`加成後攻擊力 = ${baseAttack}`);

        // Combo
        var perAttack = Math.round(baseAttack / character.combo);   // 四捨五入
        log(`單下攻擊力   = ${perAttack} (${character.combo} Combo)`);

        // Timing
        var combo = character.combo;
        var timing = 0;
        switch (member.timing) {
            case "bad":      combo -= 3;  chain =  1;    timing = 0;       break;
            case "good":     combo -= 3;  chain += 0;    timing = 0.3;     break;
            case "great":    combo -= 1;  chain += 0.1;  timing = 0.5939;  break;
            case "perfect":  combo -= 0;  chain += 0.3;  timing = 0.8999;  break;
            case "miss":     combo -= 0;  chain =  1;    timing = 0;       break;
        }
        chain = Math.round(chain * 10) / 10;

        // Damage
        var perDamage = perAttack - enemy.defense;
        if (perDamage < 1) perDamage = 1;

        var finalDamage = perAttack * (character.combo * timing + 1) - enemy.defense;
        finalDamage = Math.round(finalDamage);
        if (finalDamage < 1) finalDamage = 1;

        log(`前 ${combo - 1} 下傷害  = ${perDamage}`);
        log(`最終傷害     = ${finalDamage} (${member.timing.toUpperCase()})\n`);

        // 累計傷害
        log("累計傷害：");
        var sum = 0;
        for (var i = 0; i < combo - 1; i++) {
            sum += perDamage;
            log(sum);
        }
        log(`${sum + perDamage} => ${sum + finalDamage}`);
        sum += finalDamage;

        // Total
        total.attack += sum;
        total.combo += combo;

        log(`\nCombo = ${total.combo}`);
        log(`Total = ${total.attack}`);
        log("\n--------------------------------------------------------\n");
    });

    return total;
}

module.exports = attackAnalysis;
<commit_msg>[lib] captain number can be 0 ~ 2<commit_after>var characterList = require("../data/character_list.js");

function getTypeMagnification(character, enemy) {
    if (character.type === "力" && enemy.type === "技") return 2;
    if (character.type === "技" && enemy.type === "速") return 2;
    if (character.type === "速" && enemy.type === "力") return 2;
    if (character.type === "心" && enemy.type === "知") return 2;
    if (character.type === "知" && enemy.type === "心") return 2;

    if (character.type === "技" && enemy.type === "力") return 0.5;
    if (character.type === "速" && enemy.type === "技") return 0.5;
    if (character.type === "力" && enemy.type === "速") return 0.5;

    return 1;
}

function attackAnalysis(param) {
    var attackList        = param.team;
    var boatMagnification = param.boat;
    var enemy             = param.enemy;

    function log (s) {
        if (param.log === false) return;
        console.log(s);
    }

    // get the captain
    var captainList = [];
    attackList.forEach(function (member) {
        if (member.captain === true) {
            captainList.push(characterList[member.no]);
        }
    });
    if (captainList.length > 2) {
        log(`captain should not more than two: ${captainList.length}`);
        return null;
    }

    // show captain effect content
    log("隊長：");
    captainList.forEach(function (captain, n) {
        var name = captain.name.tw !== null ? captain.name.tw : captain.name.jp;
        var content = captain.captain.tw.content || captain.captain.jp.content;
        log(`${n + 1}. ${name} No.${captain.no}`);
        log("   " + content);
        log("");
    });
    log("--------------------------------------------------------\n");

    var chain = 1;
    var timingHistory = [];
    var total = {
        attack: 0,
        combo: 0
    };
    attackList.forEach(function(member, n) {
        var character = characterList[member.no];
        var name = character.name.tw !== null ? character.name.tw : character.name.jp;

        log(`第 ${n + 1} 位：${name}`);
        log(`原始攻擊力 = ${member.attack}, Combo = ${character.combo}`);

        var magnification = {
            boat: boatMagnification,
            type: getTypeMagnification(character, enemy),
            captain: 1,
            bead: member.bead,
            chain: chain
        };

        // get captain magnification
        captainList.forEach(function(captain) {
            var fn = captain.captain.magnification;
            magnification.captain *= fn({
                character: character,
                timingHistory: timingHistory
            });
        });

        // update timing history
        timingHistory.push(member.timing);

        log(
            `梅莉號：${magnification.boat}, `
          + `船長：${magnification.captain}, `
          + `屬珠：${magnification.bead}, `
          + `剋屬：${magnification.type}, `
          + `Chain：${magnification.chain}`
        );

        var baseAttack = member.attack
            * magnification.boat
            * magnification.type
            * magnification.captain
            * magnification.bead
            * magnification.chain;
        baseAttack = Math.round(baseAttack);
        log(`加成後攻擊力 = ${baseAttack}`);

        // Combo
        var perAttack = Math.round(baseAttack / character.combo);   // 四捨五入
        log(`單下攻擊力   = ${perAttack} (${character.combo} Combo)`);

        // Timing
        var combo = character.combo;
        var timing = 0;
        switch (member.timing) {
            case "bad":      combo -= 3;  chain =  1;    timing = 0;       break;
            case "good":     combo -= 3;  chain += 0;    timing = 0.3;     break;
            case "great":    combo -= 1;  chain += 0.1;  timing = 0.5939;  break;
            case "perfect":  combo -= 0;  chain += 0.3;  timing = 0.8999;  break;
            case "miss":     combo -= 0;  chain =  1;    timing = 0;       break;
        }
        chain = Math.round(chain * 10) / 10;

        // Damage
        var perDamage = perAttack - enemy.defense;
        if (perDamage < 1) perDamage = 1;

        var finalDamage = perAttack * (character.combo * timing + 1) - enemy.defense;
        finalDamage = Math.round(finalDamage);
        if (finalDamage < 1) finalDamage = 1;

        log(`前 ${combo - 1} 下傷害  = ${perDamage}`);
        log(`最終傷害     = ${finalDamage} (${member.timing.toUpperCase()})\n`);

        // 累計傷害
        log("累計傷害：");
        var sum = 0;
        for (var i = 0; i < combo - 1; i++) {
            sum += perDamage;
            log(sum);
        }
        log(`${sum + perDamage} => ${sum + finalDamage}`);
        sum += finalDamage;

        // Total
        total.attack += sum;
        total.combo += combo;

        log(`\nCombo = ${total.combo}`);
        log(`Total = ${total.attack}`);
        log("\n--------------------------------------------------------\n");
    });

    return total;
}

module.exports = attackAnalysis;
<|endoftext|>


<commit_before>'use strict'

var request = require('request'),
    common = require('./common'),
    apiUrl = common.urls.api,
    paths = common.urls.paths,
    regex = common.regex.auth,
    handleError = common.handleError

function getQueryString(params){
    return {
        'user[email]': params.email,
        'user[password]': params.password,
        grant_type: 'password'
    }
}

//input name=\\"authenticity_token\\" type=\\"hidden\\" value=\\"kYH0gnRWygR4IVksJNUpXCaaTc4jdp1lpvk2MKLmIaM=\\"
function getAutenticityToken(url, qs){
    return new Promise((resolve, reject) =>{
        request.post({ url:url, qs:qs }, (err, response, body) => {
            if(regex.authenticityToken.test(body))
                resolve(regex.authenticityToken.exec(body)[1])
             else
                reject('no autenticity token pressent')
        })
    })
}

function authenticate(params){
    return new Promise((resolve, reject) =>{
        var url = apiUrl + paths.auth
        var qs = getQueryString(params)
        request.post({ url:url, qs:qs }, (err, response, body) => {
            var response = JSON.parse(body)
            if(response.error)
                reject(response.error)
            else
                resolve({
                    authToken: regex.authenticityToken.exec(response.update)[1],
                    user: response.current_user
                })
        })
    })
}

module.exports = authenticate<commit_msg>removed unused code<commit_after>'use strict'

var request = require('request'),
    common = require('./common'),
    apiUrl = common.urls.api,
    paths = common.urls.paths,
    regex = common.regex.auth,
    handleError = common.handleError

function getQueryString(params){
    return {
        'user[email]': params.email,
        'user[password]': params.password,
        grant_type: 'password'
    }
}

function authenticate(params){
    return new Promise((resolve, reject) =>{
        var url = apiUrl + paths.auth
        var qs = getQueryString(params)
        request.post({ url:url, qs:qs }, (err, response, body) => {
            var response = JSON.parse(body)
            if(response.error)
                reject(response.error)
            else
                resolve({
                    authToken: regex.authenticityToken.exec(response.update)[1],
                    user: response.current_user
                })
        })
    })
}

module.exports = authenticate<|endoftext|>


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

/*
 * DEFINITION
 */
exports.definition = {
  mixinCallback: function(config){
    this.relations = {};
  },

  relation: function(name, options, callback){
    var self = this;

    options = options || {};
    options.model = options.model || name;
    options.name = name;

    if(!name) throw new Error('no name given!');
    if(!options.model) throw new Error('no model given!');
    if(!options.type) throw new Error('no type given!');

    Util.getModel(this.store, options.model, function(model){
      if(!model) throw new Error('Can not find a model for relation', name);
      options.model = model;

      self.relations[name] = options;

      self.getter(name, options.getter || function(){
        //this.relations is the relations object of the record!
        return this.relations[name];
      });

      self.setter(name, options.setter || function(record){
        if(record instanceof options.model){
          this.relations[name] = record;
        }else{
          this.relations[name] = new options.model(record);
        }
      });

      if(self.model && self.model.building){
        self.once('finished', function(){
          self.emit('relation_added', options);
        })
      }else{
        self.emit('relation_added', options);
      }

    });

    return this;
  },

  hasMany: function(name, options){
    options = options || {};
    options.type = 'has_many';

    options.setter = function(records){
      this.relations[name] = this.relations[name] || options.model.chain();
      this.relations[name].setInternal('collection_of', this);

      if(!(records instanceof Array)) records = [records];

      for(var i=0; i < records.length; i++){
        if(records[i]){
          this.relations[name].add(records[i]);
        }
      }

    };

    return this.relation(name, options);
  },

  belongsTo: function(name, options){
    options = options || {};
    options.type = 'belongs_to';

    return this.relation(name, options);
  }
};

/*
 * RECORD
 */
exports.record = {
  mixinCallback: function(config){
    config = config || {};
    this.relations = {};

    for(var name in this.definition.relations){
      this[name] = config[name] || null;
    }

  }
};<commit_msg>relations bugfixes<commit_after>var Util = require('../utils');

/*
 * DEFINITION
 */
exports.definition = {
  mixinCallback: function(config){
    this.relations = {};
  },

  relation: function(name, options, callback){
    var self = this;

    options = options || {};
    options.model = options.model || name;
    options.name = name;

    if(!name) throw new Error('no name given!');
    if(!options.model) throw new Error('no model given!');
    if(!options.type) throw new Error('no type given!');

    Util.getModel(this.store, options.model, function(model){
      if(!model) throw new Error('Can not find a model for relation', name);
      options.model = model;

      self.relations[name] = options;

      self.getter(name, options.getter || function(){
        //this.relations is the relations object of the record!
        return this.relations[name];
      });

      self.setter(name, options.setter || function(record){
        if(record instanceof options.model){
          this.relations[name] = record;
        }else{
          if(record){
            this.relations[name] = new options.model(record);
          }else{
            this.relations[name] = null;
          }
        }
      });

      if(self.model && self.model.building){
        self.once('finished', function(){
          self.emit('relation_added', options);
        })
      }else{
        self.emit('relation_added', options);
      }

    });

    return this;
  },

  hasMany: function(name, options){
    options = options || {};
    options.type = 'has_many';

    options.setter = function(records){
      this.relations[name] = this.relations[name] || options.model.chain();
      this.relations[name].setInternal('collection_of', this);

      if(!(records instanceof Array)) records = records ? [records] : [];

      for(var i=0; i < records.length; i++){
        if(records[i]){
          this.relations[name].add(records[i]);
        }
      }

    };

    return this.relation(name, options);
  },

  belongsTo: function(name, options){
    options = options || {};
    options.type = 'belongs_to';

    return this.relation(name, options);
  }
};

/*
 * RECORD
 */
exports.record = {
  mixinCallback: function(config){
    config = config || {};
    this.relations = {};

    for(var name in this.definition.relations){
      this[name] = config[name] || null;
    }

  }
};<|endoftext|>


<commit_before>/**
 * @overview Book-Generator Lib
 * @module bookGenerator
 * @author Dominik Sigmund
 * @version 0.1
 * @description Creates pdfs from Images
 * @memberof resilienz
 * @requires module:fs
 * @requires module:sync
 * @requires module:child_process
 * @requires bin:imagemagick
 */
 var fs = require('fs')
 var async = require('async')
 const execSync = require('child_process').execSync
 function BookGenerator (layouter, pagespath, bookpath, bookpages) {
   var self = {}
   self.pagespath = pagespath
   self.bookpath = bookpath
   self.pages = bookpages
   self.layouter = layouter
   self.createBook = createBook
   self.getBook = getBook
   return self
 }

 function createBook (actionid, language, callback) {
   var self = this
   fs.readdir(self.pagespath + '/' + actionid, function (error, files) {
     if (error) {
       fs.mkdir(self.pagespath + '/' + actionid, function (error) {
         if (error) {
           callback(error)
         } else {
           fs.readdir(self.pagespath + '/' + actionid, function (error, files) {
             if (error) {
               callback(error)
             } else {
               composeFiles(self.layouter, self.pagespath, actionid, language, self.pages, function (error, parm) {
                 if (error) {
                   callback(error)
                 } else {
                   makePDF(self.bookpath, parm, actionid, function (error) {
                     if (error) {
                       callback(error)
                     } else {
                       callback(null, self.bookpath + '/' + actionid + '.pdf')
                     }
                   })
                 }
               })
             }
           })
         }
       })
     } else {
       composeFiles(self.layouter, self.pagespath, actionid, language, self.pages, function (error, parm) {
         if (error) {
           callback(error)
         } else {
           makePDF(self.bookpath, parm, actionid, function (error) {
             if (error) {
               callback(error)
             } else {
               callback(null, self.bookpath + '/' + actionid + '.pdf')
             }
           })
         }
       })
     }
   })
 }

 function createPage (layouter, actionid, language, pagespath, page, callback) {
   layouter.createPage(actionid, language, page, function () {
     var split = false
     var target = ''
     if (page === 1 || page === 44) {
       target = pagespath + '/' + actionid + '/' + page + '.png'
     } else {
       target = pagespath + '/' + actionid + '/' + page + '_two.png'
       split = true
     }
     var parameter = ''
     if (split) {
       // convert -crop 720x1040 image.png cropped_%d.png
       execSync('convert -crop 720x1040 ' + target + ' /tmp/splitted_%d.png') // to tmp
       execSync('mv /tmp/splitted_0.png ' + pagespath + '/' + actionid + '/' + page + '.png')
       execSync('mv /tmp/splitted_1.png ' + pagespath + '/' + actionid + '/' + (page + 1) + '.png')
       parameter = pagespath + '/' + actionid + '/' + page + '.png'
       parameter += pagespath + '/' + actionid + '/' + (page + 1) + '.png'
     } else {
       parameter = target
     }
     callback(null, parameter)
   })
 }

 function composeFiles (layouter, pagespath, actionid, language, maxPages, callback) {
   var listOfTasks = []
   var pages = []
   for (var i = 1; i <= maxPages; i++) {
     if (i !== 1 && i % 2 !== 0) continue
     pages.push(i)
   }
   pages.forEach(function (listItem, index) {
     listOfTasks.push(function (callback) { createPage(layouter, actionid, language, pagespath, listItem, callback) })
   })
   async.series(listOfTasks, function (error, parameters) {
     if (error) {
       callback(error)
     } else {
       callback(null, parameters.join(' '))
     }
   })
 }

 function makePDF (bookpath, parameter, actionid, callback) {
   var cmd = 'convert ' + parameter + ' -compress Zip ' + bookpath + '/' + actionid + '.pdf'
   console.log(cmd)
   var output = execSync(cmd, true)
   if (output.stderr || output.stdout) {
     console.log(output.stdout)
     console.error(output.stderr)
     callback(output)
   } else {
     callback(null)
   }
 }

 function getBook (actionid, callback) {
   var self = this
   fs.access(self.bookpath + '/' + actionid + '.pdf', fs.constants.R_OK | fs.constants.W_OK, function (error) {
     if (error) {
       self.createBook(actionid, function (error) {
         if (error) {
           callback(error)
         } else {
           callback(null, self.bookpath + '/' + actionid + '.pdf')
         }
       })
     } else {
       callback(null, self.bookpath + '/' + actionid + '.pdf')
     }
   })
 }

 module.exports = BookGenerator
<commit_msg>async errro<commit_after>/**
 * @overview Book-Generator Lib
 * @module bookGenerator
 * @author Dominik Sigmund
 * @version 0.1
 * @description Creates pdfs from Images
 * @memberof resilienz
 * @requires module:fs
 * @requires module:sync
 * @requires module:child_process
 * @requires bin:imagemagick
 */
 var fs = require('fs')
 var async = require('async')
 const execSync = require('child_process').execSync
 function BookGenerator (layouter, pagespath, bookpath, bookpages) {
   var self = {}
   self.pagespath = pagespath
   self.bookpath = bookpath
   self.pages = bookpages
   self.layouter = layouter
   self.createBook = createBook
   self.getBook = getBook
   return self
 }

 function createBook (actionid, language, callback) {
   var self = this
   fs.readdir(self.pagespath + '/' + actionid, function (error, files) {
     if (error) {
       fs.mkdir(self.pagespath + '/' + actionid, function (error) {
         if (error) {
           callback(error)
         } else {
           fs.readdir(self.pagespath + '/' + actionid, function (error, files) {
             if (error) {
               callback(error)
             } else {
               composeFiles(self.layouter, self.pagespath, actionid, language, self.pages, function (error, parm) {
                 if (error) {
                   callback(error)
                 } else {
                   makePDF(self.bookpath, parm, actionid, function (error) {
                     if (error) {
                       callback(error)
                     } else {
                       callback(null, self.bookpath + '/' + actionid + '.pdf')
                     }
                   })
                 }
               })
             }
           })
         }
       })
     } else {
       composeFiles(self.layouter, self.pagespath, actionid, language, self.pages, function (error, parm) {
         if (error) {
           callback(error)
         } else {
           makePDF(self.bookpath, parm, actionid, function (error) {
             if (error) {
               callback(error)
             } else {
               callback(null, self.bookpath + '/' + actionid + '.pdf')
             }
           })
         }
       })
     }
   })
 }

 function composeFiles (layouter, pagespath, actionid, language, maxPages, callback) {
   var listOfTasks = []
   var pages = []
   for (var i = 1; i <= maxPages; i++) {
     if (i !== 1 && i % 2 !== 0) continue
     pages.push(i)
   }
   pages.forEach(function (listItem, index) {
     listOfTasks.push(function (callback) {
       layouter.createPage(actionid, language, listItem, function () {
         var page = listItem
         var split = false
         var target = ''
         if (page === 1 || page === 44) {
           target = pagespath + '/' + actionid + '/' + page + '.png'
         } else {
           target = pagespath + '/' + actionid + '/' + page + '_two.png'
           split = true
         }
         var parameter = ''
         if (split) {
           // convert -crop 720x1040 image.png cropped_%d.png
           execSync('convert -crop 720x1040 ' + target + ' /tmp/splitted_%d.png') // to tmp
           execSync('mv /tmp/splitted_0.png ' + pagespath + '/' + actionid + '/' + page + '.png')
           execSync('mv /tmp/splitted_1.png ' + pagespath + '/' + actionid + '/' + (page + 1) + '.png')
           parameter = pagespath + '/' + actionid + '/' + page + '.png'
           parameter += pagespath + '/' + actionid + '/' + (page + 1) + '.png'
         } else {
           parameter = target
         }
         callback(null, parameter)
       })
     })
   })
   async.series(listOfTasks, function (error, parameters) {
     if (error) {
       callback(error)
     } else {
       callback(null, parameters.join(' '))
     }
   })
 }

 function makePDF (bookpath, parameter, actionid, callback) {
   var cmd = 'convert ' + parameter + ' -compress Zip ' + bookpath + '/' + actionid + '.pdf'
   console.log(cmd)
   var output = execSync(cmd, true)
   if (output.stderr || output.stdout) {
     console.log(output.stdout)
     console.error(output.stderr)
     callback(output)
   } else {
     callback(null)
   }
 }

 function getBook (actionid, callback) {
   var self = this
   fs.access(self.bookpath + '/' + actionid + '.pdf', fs.constants.R_OK | fs.constants.W_OK, function (error) {
     if (error) {
       self.createBook(actionid, function (error) {
         if (error) {
           callback(error)
         } else {
           callback(null, self.bookpath + '/' + actionid + '.pdf')
         }
       })
     } else {
       callback(null, self.bookpath + '/' + actionid + '.pdf')
     }
   })
 }

 module.exports = BookGenerator
<|endoftext|>


<commit_before>/**
 * BuildProfiler
 * ======
 */
var inherit = require('inherit');
var _ = require('lodash');
var rangem = require('rangem');
var percentile = require('percentile');

/**
 * BuildProfiler
 * ============
 *
 * Накапливает знания о времени сборки таргетов.
 * @name BuildProfiler
 * @class
 */
module.exports = inherit(/** @lends BuildProfiler.prototype */ {

    /**
     * Конструктор.
     * @param {Object} buildTimes
     * @private
     */
    __constructor: function (buildTimes) {
        this._buildTimes = buildTimes || {};
    },

    /**
     * Устанавливает время начала работы для указанного таргета.
     * Если время не указано, то будет записано текущее.
     * @param {String} targetName
     * @param {String} techName
     * @param {Number} [time]
     */
    setStartTime: function (targetName, techName, time) {
        var targetTimes = this._buildTimes[targetName] || (this._buildTimes[targetName] = {});

        targetTimes.techName = techName;
        targetTimes.startTime = time || Date.now();
    },

    /**
     * Устанавливает время окончания работы для указанного таргета.
     * Если время не указано, то будет записано текущее.
     * @param {String} targetName
     * @param {Number} [time]
     */
    setEndTime: function (targetName, time) {
        if (this._buildTimes[targetName]) {
            this._buildTimes[targetName].endTime = time ? time : Date.now();
        }
    },

    /**
     * @param {String} targetName
     * @param {Object} targetsBuildTimes
     * @param {String[]} deps
     */
    _calculateTargetBuildTime: function (targetName, deps) {
        var buildTimes = this._buildTimes;
        var targetTimes = buildTimes[targetName];
        var totalStartTime = targetTimes.startTime;
        var totalEndTime = targetTimes.endTime;
        var selfTime = 0;

        if (deps.length) {
            var totalRange = { from: totalStartTime, to: totalEndTime };
            var depRanges = deps.map(function (depTarget) {
                var times = buildTimes[depTarget];

                return { from: times.startTime, to: times.endTime };
            });

            var selfRanges = rangem.subtract(totalRange, rangem.union(depRanges));

            selfRanges.forEach(function (range) {
                selfTime += range.to - range.from;
            });
        } else {
            selfTime = totalEndTime - totalStartTime;
        }

        var totalTime = totalEndTime - totalStartTime;

        var benchmarkObj = {
            totalTime: totalTime,
            selfTime: selfTime,
            watingTime: totalTime - selfTime
        };

        this._buildTimes[targetName] = _.assign(this._buildTimes[targetName], benchmarkObj);
    },

    /**
     * @param {Object} obj
     * @param {Function} mapper
     * @param {String} sortKey
     * @returns {Array}
     */
    objectToSortedArray: function (obj, mapper, sortKey) {
        return _(obj)
            .map(mapper)
            .compact()
            .value()
            .sort(function (a, b) { return b[sortKey] - a[sortKey]; });
    },

    /**
     * Вычисляет время выполнения каждого таргета.
     * @param {BuildGraph} graph
     * @returns {Object}
     */
    calculateBuildTimes: function (graph) {
        var _this = this;

        Object.keys(this._buildTimes).map(function (targetName) {
            var deps = graph.getDirectDeps(targetName);
            _this._calculateTargetBuildTime(targetName, deps);
        });

        return this.objectToSortedArray(
                this._buildTimes,
                function (targetBuildInfo, target) {
                    return _.assign({ target: target }, targetBuildInfo);
                },
                'selfTime'
            );
    },

    /**
     * @param {Number} buildTime
     * @param {Number} totalTime
     * @return {Number}
     */
    getBuildTimePercent: function (buildTime, totalTime) {
        return buildTime > 0
            ? parseFloat(buildTime / totalTime * 100, 2).toFixed(2)
            : 0;
    },

    /**
     * @param {Object} buildTimes
     * @returns {Array}
     */
    calculateTechMetrics: function (buildTimes) {
        var metricsByTech = {};
        var totalTime = 0;
        var _this = this;

        _.map(buildTimes, function (benchmark, target) {
            var tech = benchmark && benchmark.techName;
            var selfTime = buildTimes[target].selfTime;

            if (!tech) { return; }

            var techMetrics = metricsByTech[tech] || { callNumber: 0, buildTime: 0, buildTimes: [] };

            techMetrics.callNumber++;
            techMetrics.buildTime += selfTime;
            techMetrics.buildTimes.push(selfTime);
            totalTime += selfTime;

            metricsByTech[tech] = techMetrics;
        });

        return this.objectToSortedArray(
                metricsByTech,
                function (metrics, tech) {
                    return {
                        tech: tech,
                        callNumber: metrics.callNumber,
                        buildTime: metrics.buildTime,
                        percentile95: percentile(95, metrics.buildTimes),
                        buildTimePercent: _this.getBuildTimePercent(metrics.buildTime, totalTime)
                    };
                },
                'buildTime'
            );
    }
});
<commit_msg>feat(profiler): add timeline for targets<commit_after>/**
 * BuildProfiler
 * ======
 */
var inherit = require('inherit');
var _ = require('lodash');
var rangem = require('rangem');
var percentile = require('percentile');

/**
 * BuildProfiler
 * ============
 *
 * Накапливает знания о времени сборки таргетов.
 * @name BuildProfiler
 * @class
 */
module.exports = inherit(/** @lends BuildProfiler.prototype */ {

    /**
     * Конструктор.
     * @param {Object} buildTimes
     * @private
     */
    __constructor: function (buildTimes) {
        this._buildTimes = buildTimes || {};
    },

    /**
     * Устанавливает время начала работы для указанного таргета.
     * Если время не указано, то будет записано текущее.
     * @param {String} targetName
     * @param {String} techName
     * @param {Number} [time]
     */
    setStartTime: function (targetName, techName, time) {
        var targetTimes = this._buildTimes[targetName] || (this._buildTimes[targetName] = {});

        targetTimes.techName = techName;
        targetTimes.startTime = time || Date.now();
    },

    /**
     * Устанавливает время окончания работы для указанного таргета.
     * Если время не указано, то будет записано текущее.
     * @param {String} targetName
     * @param {Number} [time]
     */
    setEndTime: function (targetName, time) {
        if (this._buildTimes[targetName]) {
            this._buildTimes[targetName].endTime = time ? time : Date.now();
        }
    },

    /**
     * @param {String} targetName
     * @param {Object} targetsBuildTimes
     * @param {String[]} deps
     */
    _calculateTargetBuildTime: function (targetName, deps) {
        var buildTimes = this._buildTimes;
        var targetTimes = buildTimes[targetName];
        var totalStartTime = targetTimes.startTime;
        var totalEndTime = targetTimes.endTime;
        var selfTime = 0;
        var timeline = [];

        if (deps.length) {
            var totalRange = { from: totalStartTime, to: totalEndTime };
            var depRanges = deps.map(function (depTarget) {
                var times = buildTimes[depTarget];

                return { from: times.startTime, to: times.endTime };
            });

            var selfRanges = rangem.subtract(totalRange, rangem.union(depRanges));

            selfRanges.forEach(function (range) {
                selfTime += range.to - range.from;
            });

            timeline = selfRanges.map(function (range) {
                return {
                    startTime: range.from,
                    endTime: range.to
                };
            });
        } else {
            selfTime = totalEndTime - totalStartTime;
            timeline = [{
                startTime: totalStartTime,
                endTime: totalEndTime
            }];
        }

        var totalTime = totalEndTime - totalStartTime;

        var benchmarkObj = {
            totalTime: totalTime,
            selfTime: selfTime,
            watingTime: totalTime - selfTime,
            timeline: timeline
        };

        this._buildTimes[targetName] = _.assign(this._buildTimes[targetName], benchmarkObj);
    },

    /**
     * @param {Object} obj
     * @param {Function} mapper
     * @param {String} sortKey
     * @returns {Array}
     */
    objectToSortedArray: function (obj, mapper, sortKey) {
        return _(obj)
            .map(mapper)
            .compact()
            .value()
            .sort(function (a, b) { return b[sortKey] - a[sortKey]; });
    },

    /**
     * Вычисляет время выполнения каждого таргета.
     * @param {BuildGraph} graph
     * @returns {Object}
     */
    calculateBuildTimes: function (graph) {
        var _this = this;

        Object.keys(this._buildTimes).map(function (targetName) {
            var deps = graph.getDirectDeps(targetName);
            _this._calculateTargetBuildTime(targetName, deps);
        });

        return this.objectToSortedArray(
                this._buildTimes,
                function (targetBuildInfo, target) {
                    return _.assign({ target: target }, targetBuildInfo);
                },
                'selfTime'
            );
    },

    /**
     * @param {Number} buildTime
     * @param {Number} totalTime
     * @return {Number}
     */
    getBuildTimePercent: function (buildTime, totalTime) {
        return buildTime > 0
            ? parseFloat(buildTime / totalTime * 100, 2).toFixed(2)
            : 0;
    },

    /**
     * @param {Object} buildTimes
     * @returns {Array}
     */
    calculateTechMetrics: function (buildTimes) {
        var metricsByTech = {};
        var totalTime = 0;
        var _this = this;

        _.map(buildTimes, function (benchmark, target) {
            var tech = benchmark && benchmark.techName;
            var selfTime = buildTimes[target].selfTime;

            if (!tech) { return; }

            var techMetrics = metricsByTech[tech] || { callNumber: 0, buildTime: 0, buildTimes: [] };

            techMetrics.callNumber++;
            techMetrics.buildTime += selfTime;
            techMetrics.buildTimes.push(selfTime);
            totalTime += selfTime;

            metricsByTech[tech] = techMetrics;
        });

        return this.objectToSortedArray(
                metricsByTech,
                function (metrics, tech) {
                    return {
                        tech: tech,
                        callNumber: metrics.callNumber,
                        buildTime: metrics.buildTime,
                        percentile95: percentile(95, metrics.buildTimes),
                        buildTimePercent: _this.getBuildTimePercent(metrics.buildTime, totalTime)
                    };
                },
                'buildTime'
            );
    }
});
<|endoftext|>


<commit_before>const async = require('async');
const fs = require('fs');

const config = require('../config');
const utils = require('./utils');

const execCommand = utils.execCommand;
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;

/**
 * Run `bumpdocs` on the aframe-site repository and deploy.
 */
function bumpAframeDocs (data) {
  if (!shouldBumpDocs(data)) { return Promise.resolve(false); }

  return new Promise(resolve => {
    console.log(`Bumping A-Frame docs...`);
    async.series([
      execCommand('git reset --hard HEAD', 'aframe-site'),
      execCommand('git reset --hard HEAD', 'aframevr.github.io'),
      execCommand('git pull --rebase origin master', 'aframe-site'),
      execCommand('git pull --rebase origin master', 'aframevr.github.io'),
      execCommand(
        'node --max-old-space-size=150 /app/.heroku/node/bin/npm install --only="dev"',
        'aframe-site'),
      execCommand('node --max-old-space-size=150 /app/.heroku/node/bin/npm install',
                  'aframe-site'),
      execCommand(
        'MULTIDEP_NPM_PATH=/app/.heroku/node/bin/npm MULTIDEP_MEMORY_LIMIT=75 ' +
        'npm run bumpdocs', 'aframe-site'),
      execCommand('npm run generate', 'aframe-site'),
      execCommand('cp -r public/* ../aframevr.github.io', 'aframe-site'),
      execCommand('git status', 'aframevr.github.io'),
      execCommand('git add .', 'aframevr.github.io'),
      execCommand(`git commit -m "Bump A-Frame documentation (${data.compare})."`,
                  'aframevr.github.io'),
      execCommand(
        `git push https://${GITHUB_TOKEN}@github.com/${config.repoSitePages}.git master`,
        'aframevr.github.io')
    ], function asyncSeriesDone (err) {
      if (err) { return console.error(err); }
      console.log(`A-Frame docs successfully bumped!`);
      resolve(true);
    });
  });
}
module.exports.bumpAframeDocs = bumpAframeDocs;

/**
 * Check if A-Frame commit has changes to the documentation.
 */
function shouldBumpDocs (data) {
  // Limit to master branch and documentation branches.
  if (data.ref !== 'refs/heads/master' && !data.ref.startsWith('refs/heads/docs-v')) {
    return false;
  }

  function commitHasDocsChanges (commit) {
    return commit.modified.filter(function (file) {
      return file.startsWith('docs/') && file.endsWith('.md');
    }).length !== 0;
  }

  let hasDocsChanges = false;
  data.commits.forEach(commit => {
    if (commitHasDocsChanges(commit)) {
      hasDocsChanges = true;
    }
  });

  return hasDocsChanges;
}
module.exports.shouldBumpDocs = shouldBumpDocs;
<commit_msg>increase mem<commit_after>const async = require('async');
const fs = require('fs');

const config = require('../config');
const utils = require('./utils');

const execCommand = utils.execCommand;
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;

/**
 * Run `bumpdocs` on the aframe-site repository and deploy.
 */
function bumpAframeDocs (data) {
  if (!shouldBumpDocs(data)) { return Promise.resolve(false); }

  return new Promise(resolve => {
    console.log(`Bumping A-Frame docs...`);
    async.series([
      execCommand('git reset --hard HEAD', 'aframe-site'),
      execCommand('git reset --hard HEAD', 'aframevr.github.io'),
      execCommand('git pull --rebase origin master', 'aframe-site'),
      execCommand('git pull --rebase origin master', 'aframevr.github.io'),
      execCommand(
        'node --max-old-space-size=200 /app/.heroku/node/bin/npm install --only="dev"',
        'aframe-site'),
      execCommand('node --max-old-space-size=200 /app/.heroku/node/bin/npm install',
                  'aframe-site'),
      execCommand(
        'MULTIDEP_NPM_PATH=/app/.heroku/node/bin/npm MULTIDEP_MEMORY_LIMIT=200 ' +
        'npm run bumpdocs', 'aframe-site'),
      execCommand('npm run generate', 'aframe-site'),
      execCommand('cp -r public/* ../aframevr.github.io', 'aframe-site'),
      execCommand('git status', 'aframevr.github.io'),
      execCommand('git add .', 'aframevr.github.io'),
      execCommand(`git commit -m "Bump A-Frame documentation (${data.compare})."`,
                  'aframevr.github.io'),
      execCommand(
        `git push https://${GITHUB_TOKEN}@github.com/${config.repoSitePages}.git master`,
        'aframevr.github.io')
    ], function asyncSeriesDone (err) {
      if (err) { return console.error(err); }
      console.log(`A-Frame docs successfully bumped!`);
      resolve(true);
    });
  });
}
module.exports.bumpAframeDocs = bumpAframeDocs;

/**
 * Check if A-Frame commit has changes to the documentation.
 */
function shouldBumpDocs (data) {
  // Limit to master branch and documentation branches.
  if (data.ref !== 'refs/heads/master' && !data.ref.startsWith('refs/heads/docs-v')) {
    return false;
  }

  function commitHasDocsChanges (commit) {
    return commit.modified.filter(function (file) {
      return file.startsWith('docs/') && file.endsWith('.md');
    }).length !== 0;
  }

  let hasDocsChanges = false;
  data.commits.forEach(commit => {
    if (commitHasDocsChanges(commit)) {
      hasDocsChanges = true;
    }
  });

  return hasDocsChanges;
}
module.exports.shouldBumpDocs = shouldBumpDocs;
<|endoftext|>


<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var common = require('./common.js');
var parsing = require('./parsing.js');

module.exports = function () {
  var parser = common.makeParser(
    {
      BasalProfileStart: [
        common.makeCommonVals(),
        {
          uploadSeqNum: parsing.asNumber('Raw-Seq Num'),
          deliveryType: 'scheduled',
          scheduleName: parsing.extract(['Raw-Values', 'PATTERN_NAME']),
          rate: parsing.asNumber(['Raw-Values', 'RATE'])
        }
      ],
      ChangeTempBasalPercent: [
        common.makeCommonVals(),
        {
          deliveryType: 'temp',
          percent: parsing.map(['Raw-Values', 'PERCENT_OF_RATE'], function(e){ return parseFloat(e) / 100.0; }),
          duration: parsing.asNumber(['Raw-Values', 'DURATION'])
        }
      ],
      ChangeTempBasal: [
        common.makeCommonVals(),
        {
          deliveryType: 'temp',
          rate: parsing.asNumber(['Raw-Values', 'RATE']),
          duration: parsing.asNumber(['Raw-Values', 'DURATION'])
        }
      ]
    }
  );

  return function (simulator, data) {
    var parsed = parser(data);
    // if parsed *is* null, we're just in a row of the CSV
    // that isn't relevant to this processor
    // hence the lack of an `else` condition
    if (parsed != null) {
      switch (parsed.deliveryType) {
        case 'scheduled':
          simulator.basalScheduled(parsed);
          break;
        case 'temp':
          if (parsed.duration !== 0) {
            simulator.basalTemp(parsed);
          }
          break;
        default:
      }
    }
  };
};
<commit_msg>factor out keys for basal-related rows as constants<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2014, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

var common = require('./common.js');
var parsing = require('./parsing.js');

var RAW_VALUES = 'Raw-Values', RAW_ID = 'Raw-ID', RAW_SEQ_NUM = 'Raw-Seq Num';

var RV_KEYS = {
  DURATION: 'DURATION',
  PATTERN_NAME: 'PATTERN_NAME',
  PERCENT_OF_RATE: 'PERCENT_OF_RATE',
  RATE: 'RATE'
};

module.exports = function () {
  var parser = common.makeParser(
    {
      BasalProfileStart: [
        common.makeCommonVals(),
        {
          uploadSeqNum: parsing.asNumber(RAW_SEQ_NUM),
          deliveryType: 'scheduled',
          scheduleName: parsing.extract([RAW_VALUES, RV_KEYS.PATTERN_NAME]),
          rate: parsing.asNumber([RAW_VALUES, RV_KEYS.RATE])
        }
      ],
      ChangeTempBasalPercent: [
        common.makeCommonVals(),
        {
          deliveryType: 'temp',
          percent: parsing.map([RAW_VALUES, RV_KEYS.PERCENT_OF_RATE], function(e){ return parseFloat(e) / 100.0; }),
          duration: parsing.asNumber([RAW_VALUES, RV_KEYS.DURATION])
        }
      ],
      ChangeTempBasal: [
        common.makeCommonVals(),
        {
          deliveryType: 'temp',
          rate: parsing.asNumber([RAW_VALUES, RV_KEYS.RATE]),
          duration: parsing.asNumber([RAW_VALUES, RV_KEYS.DURATION])
        }
      ]
    }
  );

  return function (simulator, data) {
    var parsed = parser(data);
    // if parsed *is* null, we're just in a row of the CSV
    // that isn't relevant to this processor
    // hence the lack of an `else` condition
    if (parsed != null) {
      switch (parsed.deliveryType) {
        case 'scheduled':
          simulator.basalScheduled(parsed);
          break;
        case 'temp':
          if (parsed.duration !== 0) {
            simulator.basalTemp(parsed);
          }
          break;
        default:
      }
    }
  };
};
<|endoftext|>


<commit_before>'use strict';
var _ = require('lodash');
var EventEmitter = require('events').EventEmitter;
var debug = require('debug')('msb:channelManager');
var validateWithSchema = require('./validateWithSchema');
var messageFactory = require('./messageFactory');
var helpers = require('./support/helpers');
var logger = require('./support/logger');

var channelManager = exports;

var ADAPTER_PATHS = {
  amqp: './adapters/amqp',
  local: './adapters/local'
};

channelManager.create = function() {
  var channelManager = new EventEmitter();
  var config = require('./config').create();

  var producersByTopic = {};
  var consumersByTopic = {};
  var consumerTopicsToCheck = [];
  var toCheckConsumers = false;
  var adapter;
  var adapterConfig;

  channelManager.PRODUCER_NEW_TOPIC_EVENT = 'newProducerOnTopic';
  channelManager.PRODUCER_REMOVED_TOPIC_EVENT = 'removedProducerOnTopic';
  channelManager.PRODUCER_NEW_MESSAGE_EVENT = 'newProducedMessage';
  channelManager.CONSUMER_NEW_TOPIC_EVENT = 'newConsumerOnTopic';
  channelManager.CONSUMER_REMOVED_TOPIC_EVENT = 'removedConsumerOnTopic';
  channelManager.CONSUMER_NEW_MESSAGE_EVENT = 'newConsumedMessage';

  channelManager.close = function() {
    if (!adapter || !adapter.close) return;
    adapter.close();
  };

  channelManager.hasChannels = function() {
    return (Object.keys(producersByTopic).length || Object.keys(consumersByTopic).length) > 0;
  };

  channelManager.configure = function(newConfig) {
    if (channelManager.hasChannels()) {
      logger.warn('`configure()` must be called before channels are created.');
    }
    config.configure(newConfig);
    return channelManager;
  };

  channelManager.findOrCreateProducer = function(topic, options, unusedChannelTimeoutMs) {
    var channel = producersByTopic[topic];
    if (channel) return channel;
    options = options ? options : {};

    channel = producersByTopic[topic] = channelManager.createRawProducer(topic, options);

    var unusedChannelTimeout;

    channelManager.emit(channelManager.PRODUCER_NEW_TOPIC_EVENT, topic);

    channel.publish_ = channel.publish;
    channel.publish = function(message, cb) {
      clearTimeout(unusedChannelTimeout);

      if (unusedChannelTimeoutMs) {
        unusedChannelTimeout = setTimeout(onUnusedChannelTimeout, unusedChannelTimeoutMs);
      }

      channel.publish_(message, function(err) {
        if (err) return cb(err);
        channelManager.emit(channelManager.PRODUCER_NEW_MESSAGE_EVENT, topic);
        cb();
      });
    };

    function onUnusedChannelTimeout() {
      removeProducer(channel, topic);
    }

    return channel;
  };

  channelManager.createRawProducer = function(topic, options) {
    var producerConfig = _.merge(adapterConfig, options);

    return getAdapter().Publish(producerConfig).channel(helpers.validatedTopic(topic));
  };

  channelManager.findOrCreateConsumer = function(topic, options) {
    var channel = consumersByTopic[topic];
    if (channel) return channel;

    var isServiceChannel = (topic[0] === '_');
    channel = consumersByTopic[topic] = new EventEmitter();
    channel.raw = channelManager.createRawConsumer(topic, options);
    channel.setMaxListeners(0);

    var autoConfirm;
    if (options && 'autoConfirm' in options) {
      autoConfirm = options.autoConfirm;
    } else {
      autoConfirm = adapterConfig && adapterConfig.autoConfirm;
    }

    channel.onceConsuming = (channel.raw.onceConsuming) ? function(cb) {
      channel.raw.onceConsuming(cb);
      return channel;
    } : noopCb;

    channel.rejectMessage = (channel.raw.rejectMessage) ? function(message) {
      channel.raw.rejectMessage(message);
    } : noop;

    channel.confirmProcessedMessage = (channel.raw.confirmProcessedMessage) ? function(message, _safe) {
      // Only use _safe if you can't know whether message has already been confirmed/rejected
      channel.raw.confirmProcessedMessage(message, _safe);
    } : noop;

    function onMessage(message) {
      if (messageHasExpired(message)) {
        channel.rejectMessage(message);
        return;
      }
      channelManager.emit(channelManager.CONSUMER_NEW_MESSAGE_EVENT, topic);

      if (config.autoMessageContext) messageFactory.startContext(message);
      channel.emit('message', message);
      if (config.autoMessageContext) messageFactory.endContext();

      if (autoConfirm) channel.confirmProcessedMessage(message, true);
    }

    function onValidationError(err, message) {
      channel.rejectMessage(message);
    }

    // Validate with envelope schema
    if (!isServiceChannel && config.schema) {
      channel.raw.on('message', validateWithSchema.onEvent(config.schema, onMessage, onValidationError));
    } else {
      channel.raw.on('message', onMessage);
    }

    channel.on('error', function(err, message) {
      if (autoConfirm && message) {
        // Reject when a message has generated an error, e.g. not validated
        channel.rejectMessage(message);
      }
    });

    channel.raw.on('error', channel.emit.bind(channel, 'error'));

    channelManager.emit(channelManager.CONSUMER_NEW_TOPIC_EVENT, topic);

    if (isServiceChannel || !config.cleanupConsumers) return channel;

    channel.on('removeListener', function(eventName) {
      if (eventName !== 'message') return;
      if (~consumerTopicsToCheck.indexOf(topic) || channel.listeners(eventName).length) return;
      consumerTopicsToCheck.push(topic);

      if (consumerTopicsToCheck.length > 1) return;
      setImmediate(checkConsumers);
    });

    return channel;
  };

  channelManager.createRawConsumer = function(topic, options) {
    var a = getAdapter();

    var subscriberConfig = _.merge({
      channel: helpers.validatedTopic(topic)
    }, adapterConfig, options);

    return a.Subscribe(subscriberConfig);
  };

  // Backward-compatibility
  channelManager.createProducer = channelManager.createRawProducer;
  channelManager.createConsumer = channelManager.createRawConsumer;

  function getAdapter() {
    if (adapter) return adapter;

    adapterConfig = config[config.brokerAdapter];
    if (!adapterConfig) throw new Error('Invalid broker adapter \'' + config.brokerAdapter + '\'');

    adapter = require(ADAPTER_PATHS[config.brokerAdapter]).create();
    return adapter;
  }

  function checkConsumers() {
    consumerTopicsToCheck.forEach(function(topic) {
      var channel = consumersByTopic[topic];
      if (channel.listeners('message').length) return; // Still has listeners

      removeConsumer(channel, topic);
    });
    consumerTopicsToCheck = [];
  }

  function removeConsumer(channel, topic) {
    delete(consumersByTopic[topic]);
    channel.raw.close();
    channelManager.emit(channelManager.CONSUMER_REMOVED_TOPIC_EVENT, topic);
  }

  function removeProducer(channel, topic) {
    delete(producersByTopic[topic]);
    channel.close();
    channelManager.emit(channelManager.PRODUCER_REMOVED_TOPIC_EVENT, topic);
  }

  if (process.env.NODE_ENV === 'test') {
    channelManager._producersByTopic = producersByTopic;
    channelManager._consumersByTopic = consumersByTopic;
    channelManager._config = config;
  }

  return channelManager;
};

channelManager.default = channelManager.create();

function messageHasExpired(message) {
  return message.meta && message.meta.ttl &&
    new Date(message.meta.createdAt) < new Date(Date.now() - message.meta.ttl);
}

function noop() {}

function noopCb(cb) {
  cb();
}
<commit_msg>Fix adapter config not loaded for publishers<commit_after>'use strict';
var _ = require('lodash');
var EventEmitter = require('events').EventEmitter;
var debug = require('debug')('msb:channelManager');
var validateWithSchema = require('./validateWithSchema');
var messageFactory = require('./messageFactory');
var helpers = require('./support/helpers');
var logger = require('./support/logger');

var channelManager = exports;

var ADAPTER_PATHS = {
  amqp: './adapters/amqp',
  local: './adapters/local'
};

channelManager.create = function() {
  var channelManager = new EventEmitter();
  var config = require('./config').create();

  var producersByTopic = {};
  var consumersByTopic = {};
  var consumerTopicsToCheck = [];
  var toCheckConsumers = false;
  var adapter;
  var adapterConfig;

  channelManager.PRODUCER_NEW_TOPIC_EVENT = 'newProducerOnTopic';
  channelManager.PRODUCER_REMOVED_TOPIC_EVENT = 'removedProducerOnTopic';
  channelManager.PRODUCER_NEW_MESSAGE_EVENT = 'newProducedMessage';
  channelManager.CONSUMER_NEW_TOPIC_EVENT = 'newConsumerOnTopic';
  channelManager.CONSUMER_REMOVED_TOPIC_EVENT = 'removedConsumerOnTopic';
  channelManager.CONSUMER_NEW_MESSAGE_EVENT = 'newConsumedMessage';

  channelManager.close = function() {
    if (!adapter || !adapter.close) return;
    adapter.close();
  };

  channelManager.hasChannels = function() {
    return (Object.keys(producersByTopic).length || Object.keys(consumersByTopic).length) > 0;
  };

  channelManager.configure = function(newConfig) {
    if (channelManager.hasChannels()) {
      logger.warn('`configure()` must be called before channels are created.');
    }
    config.configure(newConfig);
    return channelManager;
  };

  channelManager.findOrCreateProducer = function(topic, options, unusedChannelTimeoutMs) {
    var channel = producersByTopic[topic];
    if (channel) return channel;
    options = options ? options : {};

    channel = producersByTopic[topic] = channelManager.createRawProducer(topic, options);

    var unusedChannelTimeout;

    channelManager.emit(channelManager.PRODUCER_NEW_TOPIC_EVENT, topic);

    channel.publish_ = channel.publish;
    channel.publish = function(message, cb) {
      clearTimeout(unusedChannelTimeout);

      if (unusedChannelTimeoutMs) {
        unusedChannelTimeout = setTimeout(onUnusedChannelTimeout, unusedChannelTimeoutMs);
      }

      channel.publish_(message, function(err) {
        if (err) return cb(err);
        channelManager.emit(channelManager.PRODUCER_NEW_MESSAGE_EVENT, topic);
        cb();
      });
    };

    function onUnusedChannelTimeout() {
      removeProducer(channel, topic);
    }

    return channel;
  };

  channelManager.createRawProducer = function(topic, options) {
    var a = getAdapter(); // Adapter must be loaded before config is used

    var producerConfig = _.merge(adapterConfig, options);

    return a.Publish(producerConfig).channel(helpers.validatedTopic(topic));
  };

  channelManager.findOrCreateConsumer = function(topic, options) {
    var channel = consumersByTopic[topic];
    if (channel) return channel;

    var isServiceChannel = (topic[0] === '_');
    channel = consumersByTopic[topic] = new EventEmitter();
    channel.raw = channelManager.createRawConsumer(topic, options);
    channel.setMaxListeners(0);

    var autoConfirm;
    if (options && 'autoConfirm' in options) {
      autoConfirm = options.autoConfirm;
    } else {
      autoConfirm = adapterConfig && adapterConfig.autoConfirm;
    }

    channel.onceConsuming = (channel.raw.onceConsuming) ? function(cb) {
      channel.raw.onceConsuming(cb);
      return channel;
    } : noopCb;

    channel.rejectMessage = (channel.raw.rejectMessage) ? function(message) {
      channel.raw.rejectMessage(message);
    } : noop;

    channel.confirmProcessedMessage = (channel.raw.confirmProcessedMessage) ? function(message, _safe) {
      // Only use _safe if you can't know whether message has already been confirmed/rejected
      channel.raw.confirmProcessedMessage(message, _safe);
    } : noop;

    function onMessage(message) {
      if (messageHasExpired(message)) {
        channel.rejectMessage(message);
        return;
      }
      channelManager.emit(channelManager.CONSUMER_NEW_MESSAGE_EVENT, topic);

      if (config.autoMessageContext) messageFactory.startContext(message);
      channel.emit('message', message);
      if (config.autoMessageContext) messageFactory.endContext();

      if (autoConfirm) channel.confirmProcessedMessage(message, true);
    }

    function onValidationError(err, message) {
      channel.rejectMessage(message);
    }

    // Validate with envelope schema
    if (!isServiceChannel && config.schema) {
      channel.raw.on('message', validateWithSchema.onEvent(config.schema, onMessage, onValidationError));
    } else {
      channel.raw.on('message', onMessage);
    }

    channel.on('error', function(err, message) {
      if (autoConfirm && message) {
        // Reject when a message has generated an error, e.g. not validated
        channel.rejectMessage(message);
      }
    });

    channel.raw.on('error', channel.emit.bind(channel, 'error'));

    channelManager.emit(channelManager.CONSUMER_NEW_TOPIC_EVENT, topic);

    if (isServiceChannel || !config.cleanupConsumers) return channel;

    channel.on('removeListener', function(eventName) {
      if (eventName !== 'message') return;
      if (~consumerTopicsToCheck.indexOf(topic) || channel.listeners(eventName).length) return;
      consumerTopicsToCheck.push(topic);

      if (consumerTopicsToCheck.length > 1) return;
      setImmediate(checkConsumers);
    });

    return channel;
  };

  channelManager.createRawConsumer = function(topic, options) {
    var a = getAdapter(); // Adapter must be loaded before config is used

    var subscriberConfig = _.merge({
      channel: helpers.validatedTopic(topic)
    }, adapterConfig, options);

    return a.Subscribe(subscriberConfig);
  };

  // Backward-compatibility
  channelManager.createProducer = channelManager.createRawProducer;
  channelManager.createConsumer = channelManager.createRawConsumer;

  function getAdapter() {
    if (adapter) return adapter;

    adapterConfig = config[config.brokerAdapter];
    if (!adapterConfig) throw new Error('Invalid broker adapter \'' + config.brokerAdapter + '\'');

    adapter = require(ADAPTER_PATHS[config.brokerAdapter]).create();
    return adapter;
  }

  function checkConsumers() {
    consumerTopicsToCheck.forEach(function(topic) {
      var channel = consumersByTopic[topic];
      if (channel.listeners('message').length) return; // Still has listeners

      removeConsumer(channel, topic);
    });
    consumerTopicsToCheck = [];
  }

  function removeConsumer(channel, topic) {
    delete(consumersByTopic[topic]);
    channel.raw.close();
    channelManager.emit(channelManager.CONSUMER_REMOVED_TOPIC_EVENT, topic);
  }

  function removeProducer(channel, topic) {
    delete(producersByTopic[topic]);
    channel.close();
    channelManager.emit(channelManager.PRODUCER_REMOVED_TOPIC_EVENT, topic);
  }

  if (process.env.NODE_ENV === 'test') {
    channelManager._producersByTopic = producersByTopic;
    channelManager._consumersByTopic = consumersByTopic;
    channelManager._config = config;
  }

  return channelManager;
};

channelManager.default = channelManager.create();

function messageHasExpired(message) {
  return message.meta && message.meta.ttl &&
    new Date(message.meta.createdAt) < new Date(Date.now() - message.meta.ttl);
}

function noop() {}

function noopCb(cb) {
  cb();
}
<|endoftext|>


<commit_before>var redis = require('redis'),
    moment = require('moment'),
    twitter = require('twitter'),
    winchatty = require('./winchatty'),
    tweetScanner = require('./tweetScanner');

var self, redisClient, twitterClient;

function ChattyListener(config) {
    if (!(this instanceof ChattyListener)) return new ChattyListener(config);

    this.config = config;
    redisClient = redis.createClient();
    twitterClient = new twitter(this.config.api_keys);

    redisClient.on('error', function(error) {
        console.log('(ChattyListener) redis error: ' + error);
    });
}

ChattyListener.prototype.start = function() {
    self = this;
    console.log(moment().utc().format() + ' ChattyListener is waiting for events');
    self.getNewestEventId();
};

ChattyListener.prototype.stop = function() {
    // exit on fatal error
    redisClient.quit();

    console.log('ChattyListener has stopped!');
    setInterval(function() {
        console.log('ChattyListener has stopped!');
    }, 60000);
};

ChattyListener.prototype.getNewestEventId = function() {
    winchatty.getNewestEventId(function(error, response) {
        if(error) {
            console.log('(ChattyListener) Unhandled getNewestEventId error: ' + JSON.stringify(error));
            self.getNewestEventId();
            // TODO rethink this with a sensible retry interval?
            // self.stop();
        } else {
            console.log('(ChattyListener) newestEventId: ' + response.eventId);
            self.waitForEvent(null, {lastEventId: response.eventId, events: []});
        }
    });
};

ChattyListener.prototype.waitForEvent = function(error, response) {
    if(error) {
        if(error === 'ERR_TOO_MANY_EVENTS') {
            self.getNewestEventId();
        } else {
            console.log('(ChattyListener) Unhandled waitForEvent error: ' + JSON.stringify(error));
            self.getNewestEventId();
            // TODO rethink this with a sensible retry interval?
            // self.stop();
        }
    } else {
        // tiny event indicator
        process.stdout.write('.');

        // start waiting for the next event
        winchatty.waitForEvent(response.lastEventId, self.waitForEvent);

        // process the previous event(s)
        response.events.forEach(function(event) {
            if(event.eventType === 'newPost') {
                var tweets = tweetScanner(event.eventData.post.body);
                tweets.forEach(function(tweetUrl) {
                    var dateStamp = moment().utc().format('YYYY-MM-DD'),
                        tweetId = tweetUrl.match(/twitter\.com\/\w+\/status\/(\d+)/i);

                    if(tweetId !== null && tweetId.length === 2) {
                        tweetId = tweetId[1];
                        redisClient.exists('tweet:' + tweetId, function(error, response) {
                            if(error) {
                                console.log('(ChattyListener) redis.exists error: ' + JSON.stringify(error));
                            } else if(response === 0) {
                                twitterClient.get('statuses/oembed.json', {url: tweetUrl}, function(error, response) {
                                    redisClient.zincrby('daily-twitter-api', 1, dateStamp);

                                    if(error || !('html' in response)) {
                                        console.log('(ChattyListener) twitter module error:' + JSON.stringify(error || 'no html in Twitter oembed response'));
                                    } else {
                                        console.log('(ChattyListener) caching tweet:' + tweetId);
                                        redisClient.hmset('tweet:' + tweetId, 'body', response.html, 'url', tweetUrl, 'saved', moment().utc().format());
                                        redisClient.zincrby('daily-served', 1, dateStamp);
                                        redisClient.zincrby('tweet-requests', 0, 'tweet:' + tweetId);
                                    }
                                });
                            }
                        });
                    }
                });
            }
        });
    }
};

module.exports = ChattyListener;<commit_msg>be honest<commit_after>var redis = require('redis'),
    moment = require('moment'),
    twitter = require('twitter'),
    winchatty = require('./winchatty'),
    tweetScanner = require('./tweetScanner');

var self, redisClient, twitterClient;

function ChattyListener(config) {
    if (!(this instanceof ChattyListener)) return new ChattyListener(config);

    this.config = config;
    redisClient = redis.createClient();
    twitterClient = new twitter(this.config.api_keys);

    redisClient.on('error', function(error) {
        console.log('(ChattyListener) redis error: ' + error);
    });
}

ChattyListener.prototype.start = function() {
    self = this;
    console.log(moment().utc().format() + ' ChattyListener is waiting for events');
    self.getNewestEventId();
};

ChattyListener.prototype.stop = function() {
    // exit on fatal error
    redisClient.quit();

    console.log('ChattyListener has stopped!');
    setInterval(function() {
        console.log('ChattyListener has stopped!');
    }, 60000);
};

ChattyListener.prototype.getNewestEventId = function() {
    winchatty.getNewestEventId(function(error, response) {
        if(error) {
            console.log('(ChattyListener) Unhandled getNewestEventId error: ' + JSON.stringify(error));
            self.getNewestEventId();
            // TODO rethink this with a sensible retry interval?
            // self.stop();
        } else {
            console.log('(ChattyListener) newestEventId: ' + response.eventId);
            self.waitForEvent(null, {lastEventId: response.eventId, events: []});
        }
    });
};

ChattyListener.prototype.waitForEvent = function(error, response) {
    if(error) {
        if(error === 'ERR_TOO_MANY_EVENTS') {
            self.getNewestEventId();
        } else {
            console.log('(ChattyListener) Unhandled waitForEvent error: ' + JSON.stringify(error));
            self.getNewestEventId();
            // TODO rethink this with a sensible retry interval?
            // self.stop();
        }
    } else {
        // tiny event indicator
        process.stdout.write('.');

        // start waiting for the next event
        winchatty.waitForEvent(response.lastEventId, self.waitForEvent);

        // process the previous event(s)
        response.events.forEach(function(event) {
            if(event.eventType === 'newPost') {
                var tweets = tweetScanner(event.eventData.post.body);
                tweets.forEach(function(tweetUrl) {
                    var dateStamp = moment().utc().format('YYYY-MM-DD'),
                        tweetId = tweetUrl.match(/twitter\.com\/\w+\/status\/(\d+)/i);

                    if(tweetId !== null && tweetId.length === 2) {
                        tweetId = tweetId[1];
                        redisClient.exists('tweet:' + tweetId, function(error, response) {
                            if(error) {
                                console.log('(ChattyListener) redis.exists error: ' + JSON.stringify(error));
                            } else if(response === 0) {
                                twitterClient.get('statuses/oembed.json', {url: tweetUrl}, function(error, response) {
                                    redisClient.zincrby('daily-twitter-api', 1, dateStamp);

                                    if(error || !('html' in response)) {
                                        console.log('(ChattyListener) twitter module error:' + JSON.stringify(error || 'no html in Twitter oembed response'));
                                    } else {
                                        console.log('(ChattyListener) caching tweet:' + tweetId);
                                        redisClient.hmset('tweet:' + tweetId, 'body', response.html, 'url', tweetUrl, 'saved', moment().utc().format());
                                        redisClient.zincrby('daily-served', 0, dateStamp);
                                        redisClient.zincrby('tweet-requests', 0, 'tweet:' + tweetId);
                                    }
                                });
                            }
                        });
                    }
                });
            }
        });
    }
};

module.exports = ChattyListener;<|endoftext|>