<commit_before>import fs from 'fs-extra'
import path from 'path'

const cwd = process.cwd()

export const copy = (source, target, notify) => {
  notify('copy', [source, target])

  try {
    const sourceStat = fs.statSync(source)

    if (sourceStat.isFile()) {
      fs.copySync(source, target)
    } else if (sourceStat.isDirectory()) {
      fs.ensureDirSync(target)
    }
    return true
  } catch (e) {
    notify('error', e)
    return false
  }
}

export const destroy = (fileordir, notify) => {
  notify('remove', fileordir)
  try {
    fs.removeSync(fileordir)
    return true
  } catch (e) {
    notify('error', e)
    return false
  }
}

export const deleteExtra = (fileordir, options, notify) => {
  if (options.delete) {
    return destroy(fileordir, notify)
  } else {
    notify('no-delete', fileordir)
    return true
  }
}

const getBase = (file, base) => base.filter((base) => file.indexOf(base) !== -1)
    .reduce((hit, base) => hit.length > base.length ? hit : base, '')

export const resolveTarget = (file, target, options) => {
  const from = path.join(cwd, getBase(file, options.base))

  return path.join(target, path.relative(from, file))
}
<commit_msg>added force options to copy<commit_after>import fs from 'fs-extra'
import path from 'path'

const cwd = process.cwd()

export const copy = (source, target, notify, force) => {
  notify('copy', [source, target])

  try {
    const sourceStat = fs.statSync(source)

    if (force || sourceStat.isFile()) {
      fs.copySync(source, target)
    } else if (sourceStat.isDirectory()) {
      fs.ensureDirSync(target)
    }
    return true
  } catch (e) {
    notify('error', e)
    return false
  }
}

export const destroy = (fileordir, notify) => {
  notify('remove', fileordir)
  try {
    fs.removeSync(fileordir)
    return true
  } catch (e) {
    notify('error', e)
    return false
  }
}

export const deleteExtra = (fileordir, options, notify) => {
  if (options.delete) {
    return destroy(fileordir, notify)
  } else {
    notify('no-delete', fileordir)
    return true
  }
}

const getBase = (file, base) => base.filter((base) => file.indexOf(base) !== -1)
    .reduce((hit, base) => hit.length > base.length ? hit : base, '')

export const resolveTarget = (file, target, options) => {
  const from = path.join(cwd, getBase(file, options.base))

  return path.join(target, path.relative(from, file))
}
<|endoftext|>


<commit_before>var evals = [ 'undefined', 'null', 'false', 'true' ];

module.exports = function(req, res, next) {
	req.query = _.mapValues(req.query, function(value, key) {
		value = decodeURIComponent(value);

		var arr;

		if(/^\//.test(value)) {
			arr = value.match(/\/(.*)\/(.*)/);
			value = new RegExp(arr[1], arr[2] || undefined);
		} else if(/=/.test(value)) {
			arr = value.split('=');

			value = {};

			value[arr[0]] = evals.indexOf(arr[1]) > -1 ? eval(arr[1]) : arr[1];
		}

		return value;
	});

	next();
};
<commit_msg>Updated regex regex.<commit_after>var evals = [ 'undefined', 'null', 'false', 'true' ];

module.exports = function(req, res, next) {
	req.query = _.mapValues(req.query, function(value, key) {
		value = decodeURIComponent(value);

		var arr;

		// TODO this test for regex strings will match paths whos last component only contain valid flags(i, g, m). For examaple: /path/img
		if(/^\/.*\/[gim]*$/.test(value)) {
			arr = value.match(/\/(.*)\/(.*)/);
			value = new RegExp(arr[1], arr[2] || undefined);
		} else if(/=/.test(value)) {
			arr = value.split('=');

			value = {};

			value[arr[0]] = evals.indexOf(arr[1]) > -1 ? eval(arr[1]) : arr[1];
		}

		return value;
	});

	next();
};
<|endoftext|>


<commit_before>var mongoose = require('../libs/mongoose');
var progressModel = require('../models/progress').progress;
var Module = require('../models/module').Module;
var Page = require('../models/page').Page;
var log = require('../libs/log')(module);
var persons = require('./persons');

exports.getData = function (req, res) {
    parsePages(function (err, progress) {
        if (err) throw err;
        res.json(progress);
    });
}

function sortData(progress) {
    progress.dates.sort(function (a, b) {
        a = new Date(a.date);
        b = new Date(b.date);
        return a > b ? -1 : a < b ? 1 : 0;
    });
}

function getPrevMonday(d) {
    d = new Date(d);
    var day = d.getDay(),
        diff = d.getDate() - day + (day == 0 ? -6:1); // adjust when day is sunday
    return new Date(d.setDate(diff));
}

function parsePages(callback) {
    var progress = new progressModel();
    //1. grab all pages
    Page.find({}, function (err, pages) {
        progress.dates = [];
        for (var i = 0; i < pages.length; i++) {
            var page = pages[i];
            var storyPoints = parseInt(page.storyPoints) == null ? 0 : parseInt(page.storyPoints);
            var pageProgress = parseInt(page.progress) == null ? 0 : parseInt(page.progress) * storyPoints / 100;
            var teamName = getTeamName(page.labels);
            var key = page.key;

            //time spent
            var devTimeSpent = 0;
            var qaTimeSpent = 0;
            for (var j = 0; j < page.worklogHistory.length; j++) {
                var worklog = page.worklogHistory[j];
                var isDeveloper = persons.isDeveloper(worklog.person);
                if(isDeveloper) {
                    devTimeSpent += parseFloat(worklog.timeSpent);
                }
                else {
                    qaTimeSpent += parseFloat(worklog.timeSpent);
                }
            }

            //dev estimated time
            var totalhours =
                storyPoints == 3 ? 8 :
                storyPoints == 5 ? 16 :
                storyPoints == 13 ? 40 :
                storyPoints == 17 ? 60 :
                storyPoints == 21 ? 80 :
                storyPoints == 34 ? 120 :
                storyPoints == 55 ? 160 : 0;
            var estimated = parseInt(page.progress) > 0 ? devTimeSpent*100/parseInt(page.progress) : totalhours;
            var strestimated = Math.floor(estimated).toString() + 'h/' + totalhours.toString();

            var lastTwoWeeks = new Date(Date.now());
            lastTwoWeeks.setHours(0, 0, 0, 0);
            lastTwoWeeks.setDate(lastTwoWeeks.getDate()-7);
            lastTwoWeeks = getPrevMonday(lastTwoWeeks);

            for (var j = 0; j < page.progressHistory.length; j++) {
                var history = page.progressHistory[j];
                var date = new Date(Date.parse(history.dateChanged));
                date.setHours(12, 0, 0, 0);
                if(date < lastTwoWeeks) {
                    continue;
                }
                var person = history.person;
                var from = parseInt(history.progressFrom);
//                if(from > 1) {
//                    if(history.progressTo == '0' ||
//                        history.progressTo == '1' ||
//                        history.progressTo == '' ||
//                        history.progressTo == null)
//                    continue;
//                }
                var to = history.progressTo == null || history.progressTo == '' ? 0 : parseInt(history.progressTo);
                var progressDiff = to - from;
                var calcStoryPoints = storyPoints * progressDiff / 100;
                var uri = page.uri;
                var blockers = page.blockers != null ? page.blockers.split("PLEXUXC") : [];
                var blockersnum = blockers.length > 0 ? blockers.length-1 : 0;

                putDataPoint(key, progress, teamName, date, calcStoryPoints, person, uri, devTimeSpent, qaTimeSpent, storyPoints, pageProgress, strestimated, blockersnum);
            }
        }

        //2. sort
        sortData(progress);

        callback(err, progress);
    })
}

function getTeamName(labels) {
    if (labels.indexOf("TeamRenaissance") > -1)
        return "TeamRenaissance";
    if (labels.indexOf("TeamInspiration") > -1)
        return "TeamInspiration";
    if (labels.indexOf("TeamNova") > -1)
        return "TeamNova";
    if (labels.indexOf("TeamLiberty") > -1)
        return "TeamLiberty";
    if (labels.indexOf("TeamViva") > -1)
        return "TeamViva";
}

function putDataPoint(key, progress, teamName, date, calcStoryPoints, person, uri, devTimeSpent, qaTimeSpent, storyPoints, pageProgress, estimated, blockersnum) {
    var dateFound = false;
    for (var k = 0; k < progress.dates.length; k++) {
        var pdate = progress.dates[k];
        if ((pdate.date - date) == 0) {
            dateFound = true;
            var teamFound = false;
            for (var b = 0; b < pdate.teams.length; b++) {
                var team = pdate.teams[b];
                if (team.name == teamName) {
                    teamFound = true;
                    var pages = team.pages;
                    if (pages) {
                        for (var l = 0; l < pages.length; l++) {
                            var page = pages[l];
                            if (page.key == key) {
                                page.person = person;
                                page.progress = page.progress + calcStoryPoints;
                                page.devspent = devTimeSpent;
                                page.qaspent = qaTimeSpent;
                                page.sumprogress = pageProgress;
                                page.storypoints = storyPoints;
                                page.estimated = estimated;
                                page.blockersnum = blockersnum;
                                return;
                            }
                        }
                        pages.push({
                            key: key,
                            progress: calcStoryPoints,
                            person: person,
                            uri: uri,
                            devspent: devTimeSpent,
                            qaspent: qaTimeSpent,
                            sumprogress: pageProgress,
                            storypoints: storyPoints,
                            estimated: estimated,
                            blockersnum: blockersnum
                    });
                    }
                    else {
                        team.pages = [{
                                key: key,
                                progress: calcStoryPoints,
                                person: person,
                                uri: uri,
                                devspent: devTimeSpent,
                                qaspent: qaTimeSpent,
                                sumprogress: pageProgress,
                                storypoints: storyPoints,
                                estimated: estimated,
                                blockersnum: blockersnum
                            }];
                    }
                }
            }
            if (!teamFound) {
                pdate.teams.push({
                    name: teamName, pages: [{
                            key: key,
                            progress: calcStoryPoints,
                            person: person,
                            uri: uri,
                            devspent: devTimeSpent,
                            qaspent: qaTimeSpent,
                            sumprogress: pageProgress,
                            storypoints: storyPoints,
                            estimated: estimated,
                            blockersnum: blockersnum
                    }]
                });
            }
        }
    }
    if (!dateFound) {
        progress.dates.push({
            date: date, teams: [{
                    name: teamName, pages: [{
                        key: key,
                        progress: calcStoryPoints,
                        person: person,
                        uri: uri,
                        devspent: devTimeSpent,
                        qaspent: qaTimeSpent,
                        sumprogress: pageProgress,
                        storypoints: storyPoints,
                        estimated: estimated,
                        blockersnum: blockersnum
                    }]
                }]
        });
    }
}
<commit_msg>fixed estimates calculation<commit_after>var mongoose = require('../libs/mongoose');
var progressModel = require('../models/progress').progress;
var Module = require('../models/module').Module;
var Page = require('../models/page').Page;
var log = require('../libs/log')(module);
var persons = require('./persons');

exports.getData = function (req, res) {
    parsePages(function (err, progress) {
        if (err) throw err;
        res.json(progress);
    });
}

function sortData(progress) {
    progress.dates.sort(function (a, b) {
        a = new Date(a.date);
        b = new Date(b.date);
        return a > b ? -1 : a < b ? 1 : 0;
    });
}

function getPrevMonday(d) {
    d = new Date(d);
    var day = d.getDay(),
        diff = d.getDate() - day + (day == 0 ? -6:1); // adjust when day is sunday
    return new Date(d.setDate(diff));
}

function parsePages(callback) {
    var progress = new progressModel();
    //1. grab all pages
    Page.find({}, function (err, pages) {
        progress.dates = [];
        for (var i = 0; i < pages.length; i++) {
            var page = pages[i];
            var storyPoints = parseInt(page.storyPoints) == null ? 0 : parseInt(page.storyPoints);
            var pageProgress = parseInt(page.progress) == null ? 0 : parseInt(page.progress) * storyPoints / 100;
            var teamName = getTeamName(page.labels);
            var key = page.key;

            //time spent
            var devTimeSpent = 0;
            var qaTimeSpent = 0;
            for (var j = 0; j < page.worklogHistory.length; j++) {
                var worklog = page.worklogHistory[j];
                var isDeveloper = persons.isDeveloper(worklog.person);
                if(isDeveloper) {
                    devTimeSpent += parseFloat(worklog.timeSpent);
                }
                else {
                    qaTimeSpent += parseFloat(worklog.timeSpent);
                }
            }

            //dev estimated time
            var totalhours =
                storyPoints < 5 ? 8 :
                storyPoints < 13 ? 16 :
                storyPoints < 17 ? 40 :
                storyPoints < 21 ? 60 :
                storyPoints < 34 ? 80 :
                storyPoints < 55 ? 120 :
                storyPoints >= 55 ? 160 : 0;
            var estimated = parseInt(page.progress) > 0 ? devTimeSpent*100/parseInt(page.progress) : totalhours;
            var strestimated = Math.floor(estimated).toString() + 'h/' + totalhours.toString();

            var lastTwoWeeks = new Date(Date.now());
            lastTwoWeeks.setHours(0, 0, 0, 0);
            lastTwoWeeks.setDate(lastTwoWeeks.getDate()-7);
            lastTwoWeeks = getPrevMonday(lastTwoWeeks);

            for (var j = 0; j < page.progressHistory.length; j++) {
                var history = page.progressHistory[j];
                var date = new Date(Date.parse(history.dateChanged));
                date.setHours(12, 0, 0, 0);
                if(date < lastTwoWeeks) {
                    continue;
                }
                var person = history.person;
                var from = parseInt(history.progressFrom);
//                if(from > 1) {
//                    if(history.progressTo == '0' ||
//                        history.progressTo == '1' ||
//                        history.progressTo == '' ||
//                        history.progressTo == null)
//                    continue;
//                }
                var to = history.progressTo == null || history.progressTo == '' ? 0 : parseInt(history.progressTo);
                var progressDiff = to - from;
                var calcStoryPoints = storyPoints * progressDiff / 100;
                var uri = page.uri;
                var blockers = page.blockers != null ? page.blockers.split("PLEXUXC") : [];
                var blockersnum = blockers.length > 0 ? blockers.length-1 : 0;

                putDataPoint(key, progress, teamName, date, calcStoryPoints, person, uri, devTimeSpent, qaTimeSpent, storyPoints, pageProgress, strestimated, blockersnum);
            }
        }

        //2. sort
        sortData(progress);

        callback(err, progress);
    })
}

function getTeamName(labels) {
    if (labels.indexOf("TeamRenaissance") > -1)
        return "TeamRenaissance";
    if (labels.indexOf("TeamInspiration") > -1)
        return "TeamInspiration";
    if (labels.indexOf("TeamNova") > -1)
        return "TeamNova";
    if (labels.indexOf("TeamLiberty") > -1)
        return "TeamLiberty";
    if (labels.indexOf("TeamViva") > -1)
        return "TeamViva";
}

function putDataPoint(key, progress, teamName, date, calcStoryPoints, person, uri, devTimeSpent, qaTimeSpent, storyPoints, pageProgress, estimated, blockersnum) {
    var dateFound = false;
    for (var k = 0; k < progress.dates.length; k++) {
        var pdate = progress.dates[k];
        if ((pdate.date - date) == 0) {
            dateFound = true;
            var teamFound = false;
            for (var b = 0; b < pdate.teams.length; b++) {
                var team = pdate.teams[b];
                if (team.name == teamName) {
                    teamFound = true;
                    var pages = team.pages;
                    if (pages) {
                        for (var l = 0; l < pages.length; l++) {
                            var page = pages[l];
                            if (page.key == key) {
                                page.person = person;
                                page.progress = page.progress + calcStoryPoints;
                                page.devspent = devTimeSpent;
                                page.qaspent = qaTimeSpent;
                                page.sumprogress = pageProgress;
                                page.storypoints = storyPoints;
                                page.estimated = estimated;
                                page.blockersnum = blockersnum;
                                return;
                            }
                        }
                        pages.push({
                            key: key,
                            progress: calcStoryPoints,
                            person: person,
                            uri: uri,
                            devspent: devTimeSpent,
                            qaspent: qaTimeSpent,
                            sumprogress: pageProgress,
                            storypoints: storyPoints,
                            estimated: estimated,
                            blockersnum: blockersnum
                    });
                    }
                    else {
                        team.pages = [{
                                key: key,
                                progress: calcStoryPoints,
                                person: person,
                                uri: uri,
                                devspent: devTimeSpent,
                                qaspent: qaTimeSpent,
                                sumprogress: pageProgress,
                                storypoints: storyPoints,
                                estimated: estimated,
                                blockersnum: blockersnum
                            }];
                    }
                }
            }
            if (!teamFound) {
                pdate.teams.push({
                    name: teamName, pages: [{
                            key: key,
                            progress: calcStoryPoints,
                            person: person,
                            uri: uri,
                            devspent: devTimeSpent,
                            qaspent: qaTimeSpent,
                            sumprogress: pageProgress,
                            storypoints: storyPoints,
                            estimated: estimated,
                            blockersnum: blockersnum
                    }]
                });
            }
        }
    }
    if (!dateFound) {
        progress.dates.push({
            date: date, teams: [{
                    name: teamName, pages: [{
                        key: key,
                        progress: calcStoryPoints,
                        person: person,
                        uri: uri,
                        devspent: devTimeSpent,
                        qaspent: qaTimeSpent,
                        sumprogress: pageProgress,
                        storypoints: storyPoints,
                        estimated: estimated,
                        blockersnum: blockersnum
                    }]
                }]
        });
    }
}
<|endoftext|>


<commit_before>/*    Copyright 2016 Firewalla LLC / Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    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
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

 'use strict';

 const log = require('../net2/logger.js')(__filename);
 const util = require('util');
 const DataMigrator = require('./DataMigrator.js');
 let instance = null;

 const bclient = require('../util/redis_manager.js').getBufferRedisClient();
 const readline = require('readline');

 const partitionKeyMap = {
  "devices": [
    "host:*"
  ],
  "alarms": [
    "alarm:id",
    "_alarm:*",
    "_alarmDetail:*",
    "alarm_active",
    "alarm_archive"
  ],
  "rules": [
    "policy:id",
    "policy_active",
    "policy:*"
  ],
  "exceptions": [
    "exception:id",
    "exception:*",
    "exception_queue"
  ],
  "config": [
    "policy:system",
    "policy:mac:*",
    "ext.*",
    "scisurf.config",
    "sys:features",
    "mode"
  ],
  "intel": [
    "category:*",
    "dynamicCategoryDomain:*",
    "ipmapping:*",
    "intel:*",
    "lastCategory:*",
    "cache.intel:*",
    "rdns:*",
    "dns:*",
    "srdns:*",
    "ip_set_to_be_processed",
    "software:ip:*",
    "user_agent:*",
    "app:*",
    "lastapp:*",
    "dhcp:*"
  ],
  "traffic": [
    "flow:*",
    "aggrflow:*",
    "appflow:*",
    "sumflow:*",
    "syssumflow:*",
    "lastsumflow:*",
    "categoryflow:*",
    "timedTraffic:*"
  ],
  "stats": [
    "boneAPIUsage:*",
    "neighbor:*",
    "stats:*"
  ],
  "mq": [
    "bq:*"
  ],
  "misc": [
    "monitored_hosts*",
    "unmonitored_hosts*",
    "zoffline_hosts"
  ]
};

class RedisMigrator extends DataMigrator {
  constructor() {
    super();
    if (instance == null) {
      instance = this;
    }
    return instance;
  }

  async _serializeRedisKey(key) {
    let result = "";
    const keyDump = await bclient.dumpAsync(key);
    if (keyDump !== null) {
      const ttl = await bclient.pttlAsync(key); // in milliseconds
      const record = {
        key: key.toString(),
        ttl: ttl,
        value: keyDump
      };
      result = JSON.stringify(record) + "\n";
    }
    return result;
  }

  async _deserializeRedisKey(record) {
    if (record !== "") {
      const recordJson = JSON.parse(record);
      const key = recordJson.key;
      const ttl = Math.max(0, recordJson.ttl);
      const value = Buffer.from(recordJson.value.data);
      await bclient.restoreAsync(key, ttl, value, "REPLACE");
    }
  }

  async export(partition, outputStream) {
    const keyPatterns = partitionKeyMap[partition];
    if (keyPatterns !== null) {
      for (let i in keyPatterns) {
        const pattern = keyPatterns[i];
        const keys = await bclient.keysAsync(pattern);
        log.info("Pattern: " + pattern + ", number of keys to dump: " + keys.length);
        while (keys.length > 0) {
          // concurrency limit
          const keysBatch = keys.splice(0, 4);
          await Promise.all(keysBatch.map(async key => {
            const serializedData = await this._serializeRedisKey(key);
            outputStream.write(Buffer.from(serializedData));
          }));
        }
        if (global.gc)
          global.gc();
      }
    }
    outputStream.end();
  }

  async import(inputStream) {
    const lineReader = readline.createInterface({
      input: inputStream
    });
    return new Promise((resolve, reject) => {
      lineReader.on('line', (line) => {
        this._deserializeRedisKey(line);
      });
      lineReader.on('close', () => {
        if (global.gc)
          global.gc();
        resolve();
      })
    })
  }
}

module.exports = RedisMigrator;<commit_msg>add dhcp:static into migrated data<commit_after>/*    Copyright 2016 Firewalla LLC / Firewalla LLC
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    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
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

 'use strict';

 const log = require('../net2/logger.js')(__filename);
 const util = require('util');
 const DataMigrator = require('./DataMigrator.js');
 let instance = null;

 const bclient = require('../util/redis_manager.js').getBufferRedisClient();
 const readline = require('readline');

 const partitionKeyMap = {
  "devices": [
    "host:*"
  ],
  "alarms": [
    "alarm:id",
    "_alarm:*",
    "_alarmDetail:*",
    "alarm_active",
    "alarm_archive"
  ],
  "rules": [
    "policy:id",
    "policy_active",
    "policy:*"
  ],
  "exceptions": [
    "exception:id",
    "exception:*",
    "exception_queue"
  ],
  "config": [
    "policy:system",
    "policy:mac:*",
    "ext.*",
    "scisurf.config",
    "sys:features",
    "mode",
    "dhcp:static"
  ],
  "intel": [
    "category:*",
    "dynamicCategoryDomain:*",
    "ipmapping:*",
    "intel:*",
    "lastCategory:*",
    "cache.intel:*",
    "rdns:*",
    "dns:*",
    "srdns:*",
    "ip_set_to_be_processed",
    "software:ip:*",
    "user_agent:*",
    "app:*",
    "lastapp:*",
    "dhcp:*"
  ],
  "traffic": [
    "flow:*",
    "aggrflow:*",
    "appflow:*",
    "sumflow:*",
    "syssumflow:*",
    "lastsumflow:*",
    "categoryflow:*",
    "timedTraffic:*"
  ],
  "stats": [
    "boneAPIUsage:*",
    "neighbor:*",
    "stats:*"
  ],
  "mq": [
    "bq:*"
  ],
  "misc": [
    "monitored_hosts*",
    "unmonitored_hosts*",
    "zoffline_hosts"
  ]
};

class RedisMigrator extends DataMigrator {
  constructor() {
    super();
    if (instance == null) {
      instance = this;
    }
    return instance;
  }

  async _serializeRedisKey(key) {
    let result = "";
    const keyDump = await bclient.dumpAsync(key);
    if (keyDump !== null) {
      const ttl = await bclient.pttlAsync(key); // in milliseconds
      const record = {
        key: key.toString(),
        ttl: ttl,
        value: keyDump
      };
      result = JSON.stringify(record) + "\n";
    }
    return result;
  }

  async _deserializeRedisKey(record) {
    if (record !== "") {
      const recordJson = JSON.parse(record);
      const key = recordJson.key;
      const ttl = Math.max(0, recordJson.ttl);
      const value = Buffer.from(recordJson.value.data);
      await bclient.restoreAsync(key, ttl, value, "REPLACE");
    }
  }

  async export(partition, outputStream) {
    const keyPatterns = partitionKeyMap[partition];
    if (keyPatterns !== null) {
      for (let i in keyPatterns) {
        const pattern = keyPatterns[i];
        const keys = await bclient.keysAsync(pattern);
        log.info("Pattern: " + pattern + ", number of keys to dump: " + keys.length);
        while (keys.length > 0) {
          // concurrency limit
          const keysBatch = keys.splice(0, 4);
          await Promise.all(keysBatch.map(async key => {
            const serializedData = await this._serializeRedisKey(key);
            outputStream.write(Buffer.from(serializedData));
          }));
        }
        if (global.gc)
          global.gc();
      }
    }
    outputStream.end();
  }

  async import(inputStream) {
    const lineReader = readline.createInterface({
      input: inputStream
    });
    return new Promise((resolve, reject) => {
      lineReader.on('line', (line) => {
        this._deserializeRedisKey(line);
      });
      lineReader.on('close', () => {
        if (global.gc)
          global.gc();
        resolve();
      })
    })
  }
}

module.exports = RedisMigrator;<|endoftext|>


<commit_before><commit_msg>combat animation fix<commit_after><|endoftext|>


<commit_before>import _ from 'lodash'
import React, { PropTypes } from 'react'

import ComponentDescription from './ComponentDescription'
import ComponentExamples from './ComponentExamples'
import ComponentProps from './ComponentProps'
import docgenInfo from '../../docgenInfo.json'

const ComponentDoc = ({ _meta }) => {
  // TODO remove docgetInfo searching in favor of separate docgen.json in each component directory
  // this just parses out a single docgen file based on component path name
  // our current docgen gulp task concats these into one, only for us to split it back out
  const docPath = _.find(_.keys(docgenInfo), path => new RegExp(`${_meta.name}.js$`).test(path))
  const docgen = docgenInfo[docPath]

  return (

      <ComponentDescription
        _meta={_meta}
        docgen={docgen}
        docPath={docPath}
      />
      <ComponentProps props={docgen.props} />
      {docgen.props && <ComponentProps props={docgen.props} />}
      <ComponentExamples name={_meta.name} />

  )
}

ComponentDoc.propTypes = {
  _meta: PropTypes.object,
}

export default ComponentDoc
<commit_msg>fix(ComponentDoc) : remove duplicate props<commit_after>import _ from 'lodash'
import React, { PropTypes } from 'react'

import ComponentDescription from './ComponentDescription'
import ComponentExamples from './ComponentExamples'
import ComponentProps from './ComponentProps'
import docgenInfo from '../../docgenInfo.json'

const ComponentDoc = ({ _meta }) => {
  // TODO remove docgetInfo searching in favor of separate docgen.json in each component directory
  // this just parses out a single docgen file based on component path name
  // our current docgen gulp task concats these into one, only for us to split it back out
  const docPath = _.find(_.keys(docgenInfo), path => new RegExp(`${_meta.name}.js$`).test(path))
  const docgen = docgenInfo[docPath]

  return (

      <ComponentDescription
        _meta={_meta}
        docgen={docgen}
        docPath={docPath}
      />
      {docgen.props && <ComponentProps props={docgen.props} />}
      <ComponentExamples name={_meta.name} />

  )
}

ComponentDoc.propTypes = {
  _meta: PropTypes.object,
}

export default ComponentDoc
<|endoftext|>


<commit_before>/**
 * @module object
 */

/**
 * Transforms an object into a Map
 *
 * @param {Object|Map} object
 * @return {Map}
 */
exports.toMap = function(object) {
    if (object instanceof Map) {
        return object;
    }
    var map = new Map();
    for (var key in object) {
        map.set(key, object[key]);
    }
    return map;
};

/**
 * Use Object.assign, but allows empty objects
 *
 * @param {Object} target
 * @param {...Object} objects
 * @return {Object} target
 */
exports.extend = function(target) {
    if (typeof target !== 'object') {
        throw new Error('target is not an object');
    }
    var objects = Array.from(arguments).filter(function(value) { return !!value; });
    if (objects.length === 1) {
        return target;
    }
    return Object.assign.apply(null, objects);
};

/**
 *
 * @param {Object} target
 * @param {Object} object
 * @return {Object} target
 */
exports.union = function(target) {
    Array.from(arguments).slice(1).forEach((object) => {
        if (object) {
            for (var key in object) {
                if (target[key] === undefined) {
                    target[key] = object[key];
                }
            }
        }
    });
    return target;
};

/**
 * Clones an object
 *
 * @param {Object} target
 * @param {Object} object
 * @return {Object} target
 */
exports.clone = function(object) {
    return Object.assign({}, object);
};

/**
 * Filter an object by the keys
 *
 * @param  {Object} object
 * @param  {Array} keys
 * @return {Object}
 */
exports.filterKeys = function(object, keys) {
    var filteredObject = {};
    keys.forEach(function(k) {
        filteredObject[k] = object[k];
    });
    return filteredObject;
};

/**
 * Same a map + join
 *
 * @param  {Object} object
 * @param  {Array} keys
 * @return {Object}
 */
exports.mapJoin = function(object, separator, callback) {
    if (typeof separator === 'function') {
        callback = separator;
        separator = '';
    }
    object = exports.map(object, callback);
    return exports.join(object, separator);
};

// Array-like functions
var _commonObjectArray = function(propertyName, object, callback, thisArg) {
    if (!thisArg) {
        thisArg = object;
    }
    /*#if DEV*/
    if (object[propertyName]) {
        throw new Error('call ' + propertyName + ' !');
    }
    /*#/if*/
    return Object.keys(object)[propertyName](function(key) {
        return callback.call(thisArg, object[key], key);
    });
};

/**
 * The entries() method returns a new Iterator that contains the key/value pairs for each index in the Object.
*
* @param {Object|Map} object
* @return {Iterator}
*/
exports.entries = function(object) {
    var keys = Object.keys(object), i = 0;
    return Object.freeze({
        next() {
            if (i >= keys.length) {
                return Object.freeze({ done: true });
            }
            return Object.freeze({ value: [ keys[i], object[keys[i++]] ], done: false });
        }
    });
};

/**
* The filter() method creates a new object with all elements that pass the test implemented by the provided function.
*
* @param {Object} object
* @param {Function} callback
* @param {*=} thisArg
* @return {Object}
*/
exports.filter = function(object, callback, thisArg) {
    var keys = _commonObjectArray.call(null, 'filter', object, callback, thisArg);
    return exports.filterKeys(object, keys);
};

/**
* The find() method returns a value in the object,
* if an element in the object satisfies the provided testing function. Otherwise undefined is returned.
*
* @param {Object} object
* @param {Function} callback
* @param {*=} thisArg
* @return {*}
*/
exports.find = function(object, callback, thisArg) {
    var key = exports.findIndex.apply(null, arguments);
    return key && object[key];
};

/**
* The findIndex() method returns an key in the object,
* if an element in the object satisfies the provided testing function. Otherwise undefined is returned.
*
* @function
* @param {Object} object
* @param {Function} callback
* @param {*=} thisArg
* @return {String|undefined}
*/
exports.findIndex = _commonObjectArray.bind(null, 'find');

/**
* The forEach() method executes a provided function once per object element.
*
* @function
* @param {Object|Map} object
* @param {Function} callback
* @param {*} thisArg
* @return {*}
*/
exports.forEach = _commonObjectArray.bind(null, 'forEach');

/**
* The join() method joins all elements of an object into a string.
*
* @param {Object} object
* @param {String} separator
* @return {String}
*/
exports.join = function(object, separator) {
    return Object.keys(object).map(function(key) {
        return object[key];
    }).join(separator);
};

/**
* The map() method creates a new object with the results of calling a provided function on every element in this object.
*
* @param {Object} object
* @param {Function} callback
* @param {*=} thisArg
* @return {*}
*/
exports.map = function(object, callback, thisArg) {
    var mappedObject = {};
    exports.forEach(object, function(value, key) {
        mappedObject[key] = callback.apply(this, arguments);
    });
    return mappedObject;
};

/**
* The reduce() method applies a function against an accumulator
* and each value of the object (from left-to-right) has to reduce it to a single value.
*
* @param {Object} object
* @param {Function} callback
* @param {*=} initialValue
* @return {*}
*/
exports.reduce = function(object, callback, initialValue) {
    return Object.keys(object).reduce(function(previousValue, currentValue, index, array) {
        return callback(previousValue, object[currentValue], currentValue, object);
    }, initialValue);
};

/**
* The reduceRight() method applies a function against an accumulator
* and each value of the object (from right-to-left) as to reduce it to a single value.
*
* @param {Object} object
* @param {Function} callback
* @param {*=} initialValue
* @return {*}
*/
exports.reduceRight = function(object, callback, initialValue) {
    return Object.keys(object).reduceRight(function(previousValue, currentValue, index, array) {
        return callback(previousValue, object[currentValue], currentValue, object);
    }, initialValue);
};

/**
* The some() method tests whether some element in the object passes the test implemented by the provided function.
*
* @type {Function}
* @param {Object} object
* @param {Function} callback
* @param {*=} thisArg
* @return {Boolean}
*/
exports.some = _commonObjectArray.bind(null, 'some');
<commit_msg>jsdoc<commit_after>/**
 * @module object
 */

/**
 * Transforms an object into a Map
 *
 * @param {Object|Map} object
 * @return {Map}
 */
exports.toMap = function(object) {
    if (object instanceof Map) {
        return object;
    }
    var map = new Map();
    for (var key in object) {
        map.set(key, object[key]);
    }
    return map;
};

/**
 * Use Object.assign, but allows empty objects
 *
 * @param {Object} target
 * @param {...Object} objects
 * @return {Object} target
 */
exports.extend = function(target) {
    if (typeof target !== 'object') {
        throw new Error('target is not an object');
    }
    var objects = Array.from(arguments).filter(function(value) { return !!value; });
    if (objects.length === 1) {
        return target;
    }
    return Object.assign.apply(null, objects);
};

/**
 *
 * @param {Object} target
 * @param {Object} object
 * @return {Object} target
 */
exports.union = function(target) {
    Array.from(arguments).slice(1).forEach((object) => {
        if (object) {
            for (var key in object) {
                if (target[key] === undefined) {
                    target[key] = object[key];
                }
            }
        }
    });
    return target;
};

/**
 * Clones an object
 *
 * @param {Object} object
 * @return {Object} target
 */
exports.clone = function(object) {
    return Object.assign({}, object);
};

/**
 * Filter an object by the keys
 *
 * @param  {Object} object
 * @param  {Array} keys
 * @return {Object}
 */
exports.filterKeys = function(object, keys) {
    var filteredObject = {};
    keys.forEach(function(k) {
        filteredObject[k] = object[k];
    });
    return filteredObject;
};

/**
 * Same a map + join
 *
 * @param  {Object} object
 * @param  {Array} keys
 * @return {Object}
 */
exports.mapJoin = function(object, separator, callback) {
    if (typeof separator === 'function') {
        callback = separator;
        separator = '';
    }
    object = exports.map(object, callback);
    return exports.join(object, separator);
};

// Array-like functions
var _commonObjectArray = function(propertyName, object, callback, thisArg) {
    if (!thisArg) {
        thisArg = object;
    }
    /*#if DEV*/
    if (object[propertyName]) {
        throw new Error('call ' + propertyName + ' !');
    }
    /*#/if*/
    return Object.keys(object)[propertyName](function(key) {
        return callback.call(thisArg, object[key], key);
    });
};

/**
 * The entries() method returns a new Iterator that contains the key/value pairs for each index in the Object.
*
* @param {Object|Map} object
* @return {Iterator}
*/
exports.entries = function(object) {
    var keys = Object.keys(object), i = 0;
    return Object.freeze({
        next() {
            if (i >= keys.length) {
                return Object.freeze({ done: true });
            }
            return Object.freeze({ value: [ keys[i], object[keys[i++]] ], done: false });
        }
    });
};

/**
* The filter() method creates a new object with all elements that pass the test implemented by the provided function.
*
* @param {Object} object
* @param {Function} callback
* @param {*=} thisArg
* @return {Object}
*/
exports.filter = function(object, callback, thisArg) {
    var keys = _commonObjectArray.call(null, 'filter', object, callback, thisArg);
    return exports.filterKeys(object, keys);
};

/**
* The find() method returns a value in the object,
* if an element in the object satisfies the provided testing function. Otherwise undefined is returned.
*
* @param {Object} object
* @param {Function} callback
* @param {*=} thisArg
* @return {*}
*/
exports.find = function(object, callback, thisArg) {
    var key = exports.findIndex.apply(null, arguments);
    return key && object[key];
};

/**
* The findIndex() method returns an key in the object,
* if an element in the object satisfies the provided testing function. Otherwise undefined is returned.
*
* @function
* @param {Object} object
* @param {Function} callback
* @param {*=} thisArg
* @return {String|undefined}
*/
exports.findIndex = _commonObjectArray.bind(null, 'find');

/**
* The forEach() method executes a provided function once per object element.
*
* @function
* @param {Object|Map} object
* @param {Function} callback
* @param {*} thisArg
* @return {*}
*/
exports.forEach = _commonObjectArray.bind(null, 'forEach');

/**
* The join() method joins all elements of an object into a string.
*
* @param {Object} object
* @param {String} separator
* @return {String}
*/
exports.join = function(object, separator) {
    return Object.keys(object).map(function(key) {
        return object[key];
    }).join(separator);
};

/**
* The map() method creates a new object with the results of calling a provided function on every element in this object.
*
* @param {Object} object
* @param {Function} callback
* @param {*=} thisArg
* @return {*}
*/
exports.map = function(object, callback, thisArg) {
    var mappedObject = {};
    exports.forEach(object, function(value, key) {
        mappedObject[key] = callback.apply(this, arguments);
    });
    return mappedObject;
};

/**
* The reduce() method applies a function against an accumulator
* and each value of the object (from left-to-right) has to reduce it to a single value.
*
* @param {Object} object
* @param {Function} callback
* @param {*=} initialValue
* @return {*}
*/
exports.reduce = function(object, callback, initialValue) {
    return Object.keys(object).reduce(function(previousValue, currentValue, index, array) {
        return callback(previousValue, object[currentValue], currentValue, object);
    }, initialValue);
};

/**
* The reduceRight() method applies a function against an accumulator
* and each value of the object (from right-to-left) as to reduce it to a single value.
*
* @param {Object} object
* @param {Function} callback
* @param {*=} initialValue
* @return {*}
*/
exports.reduceRight = function(object, callback, initialValue) {
    return Object.keys(object).reduceRight(function(previousValue, currentValue, index, array) {
        return callback(previousValue, object[currentValue], currentValue, object);
    }, initialValue);
};

/**
* The some() method tests whether some element in the object passes the test implemented by the provided function.
*
* @type {Function}
* @param {Object} object
* @param {Function} callback
* @param {*=} thisArg
* @return {Boolean}
*/
exports.some = _commonObjectArray.bind(null, 'some');
<|endoftext|>


<commit_before>/**
 * Output video params widget.
 * @module webm/params
 */

import React from "react";
import {
  Paper, RadioButtonGroup, RadioButton, Checkbox,
  FlatButton, SelectField, ClearFix, TextField,
  RaisedButton, SmallInput,
} from "./theme";
import {ShowHide, has, getDefaultVideoThreads} from "./util";

const styles = {
  root: {
    position: "relative",
  },
  header: {
    paddingTop: 8,
    paddingLeft: 8,
    color: "#e0e0e0",
    fontWeight: 500,
    fontSize: "18px",
    textTransform: "uppercase",
  },
  section: {
    padding: "16px 24px",
    borderBottom: "1px solid #e0e0e0",
  },
  lastSection: {
    padding: "16px 24px 24px 24px",
  },
  left: {
    float: "left",
    width: "60%",
  },
  right: {
    float: "left",
    marginTop: -30,
  },
  smallSelect: {
    width: 230,
  },
  encode: {
    textAlign: "center",
    paddingBottom: 16,
  },
  bigButton: {
    width: 250,
  },
  uiMode: {
    width: 110,
    position: "absolute",
    bottom: 0,
    right: 0,
    borderRadius: 0,
  },
};

// XXX(Kagami): Singleton to satisfate `TextField.hasValue` and emulate
// filled text field because there is no other way to display floating
// label by default.
// TODO(Kagami): Request this feature.
const Empty = {
  toString: function() {
    return "";
  },
};

export default React.createClass({
  getInitialState: function() {
    return {};
  },
  componentDidMount: function() {
    this.handleUI();
    if (window.localStorage && localStorage.SKIP_PARAMS) {
      // XXX(Kagami): Workaround asynchronous setState. Not sure whether
      // it will always work, but this is debug-only anyway.
      setTimeout(this.handleEncodeClick);
    }
  },
  DEFAULT_LIMIT: 8,
  DEFAULT_QUALITY: 20,
  DEFAULT_AUIDIO_BITRATE: 64,
  getLimitLabel: function() {
    return this.state.mode === "limit" ? "limit (MiB)" : "bitrate (kbits)";
  },
  getDefaultDuration: function() {
    return window.localStorage && localStorage.DURATION || Empty;
  },
  getDurationLabel: function() {
    return this.state.useEndTime ? "end (time)" : "duration (time)";
  },
  getItems: function(type) {
    return this.props.info[type].map(track => {
      let text = `#${track.id} - ${track.codec}`;
      if (track.default) text += " (default)";
      return {payload: track.id, text};
    });
  },
  // NOTE(Kagami): We will always have video tracks because we checked
  // for them in Info component. Audio/subtitles tracks are not required
  // though.
  hasAudioTracks: function() {
    return !!this.props.info.audio.length;
  },
  hasSubsTracks: function() {
    return !!this.props.info.subs.length;
  },
  calcVideoBitrate: function({limit, audioBitrate}) {
    limit = +limit;
    audioBitrate = +audioBitrate;
    // FIXME(Kagami): Take start/end into account and check for zero
    // length/intersections/etc.
    const duration = this.props.info.duration;
    const limitKbits = limit * 8 * 1024;
    const vb = Math.floor(limitKbits / duration - audioBitrate);
    return vb > 0 ? vb : 1;
  },
  makeRawOpts: function(state) {
    // NOTE(Kagami): We accept state variables via arguments because
    // `setState` is asynchronous and values in `this.state` might be
    // outdated.
    // TODO(Kagami): scale, crop, custom filters, set/clear metadata.
    let opts = [];
    // This might be `Empty`.
    const limit = state.limit.toString();
    const quality = state.quality.toString();
    let vb = state.mode === "limit" ? this.calcVideoBitrate(state) : +limit;
    if (vb !== 0) vb += "k";
    const duration = state.duration.toString();

    // Input.
    opts.push("-i", this.props.source.safeName);
    if (duration !== "") {
      // FIXME(Kagami): Fix duration in case of `useEndTime`.
      // NOTE(Kagami): We always use `-t` in resulting command because
      // `-ss` before `-i` resets the timestamp, see:
      // <https://trac.ffmpeg.org/wiki/Seeking#Notes>.
      opts.push("-t", duration);
    }

    // Streams.

    // Video.
    // TODO(Kagami): We can improve quality a bit with "-speed 0 -g 9999".
    // Though this will slow down the encoding so we need to find the
    // best speed/quality compromise for in-browser use.
    opts.push("-c:v", "libvpx", "-speed", "1");
    opts.push("-auto-alt-ref", "1", "-lag-in-frames", "25");
    opts.push("-b:v", vb);
    if (quality !== "") opts.push("-crf", quality);

    // Audio.
    if (state.noAudio) {
      opts.push("-an");
    } else {
      opts.push("-c:a", "libopus", "-ac", "2");
      opts.push("-b:a", state.audioBitrate + "k");
    }

    // Subs.
    opts.push("-sn");

    return opts;
  },
  handleMode: function(e, mode) {
    // NOTE(Kagami): `radio.getSelectedValue` isn't always up-to-date,
    // so we need this function to get the actual value. See:
    // <https://github.com/callemall/material-ui/issues/295>.
    // Though we need this function anyway to fix some settings in the
    // moment of switch.
    let upd = {mode};
    const prevMode = this.state.mode;
    if (mode === "limit") {
      upd.limit = this.DEFAULT_LIMIT;
    } else if (mode === "bitrate") {
      if (prevMode === "limit") {
        upd.limit = this.calcVideoBitrate(this.state);
      } else if (prevMode === "constq") {
        upd.limit = "";
      }
    } else if (mode === "constq") {
      upd.limit = 0;
      if (!this.refs.quality.getValue()) {
        upd.quality = this.DEFAULT_QUALITY;
      }
    }
    this.handleUI(upd);
  },
  handleNoAudio: function(e, noAudio) {
    let audioBitrate = noAudio ? 0 : this.DEFAULT_AUIDIO_BITRATE;
    this.handleUI({audioBitrate});
  },
  // Helper to ignore passed event arguments.
  handleEvent: function() {
    this.handleUI();
  },
  handleSelect: function(name, e, index, payload) {
    let change = {};
    change[name] = payload.payload;
    this.handleUI(change);
  },
  handleRawOpts: function(e) {
    this.setState({rawOpts: e.target.value});
  },
  handleUI: function(preState) {
    preState = preState || {};
    function get(option, def) {
      return has(preState, option) ? preState[option] : def;
    }

    // Getting.
    let mode = get("mode", this.refs.mode.getSelectedValue());
    let videoTrack = get("videoTrack", this.state.videoTrack);
    let limit = get("limit", this.refs.limit.getValue());
    let quality = get("quality", this.refs.quality.getValue());
    let noAudio = this.refs.noAudio.isChecked();
    let audioTrack = get("audioTrack", this.state.audioTrack);
    let audioBitrate = get("audioBitrate", this.refs.audioBitrate.getValue());
    let useEndTime = this.refs.useEndTime.isChecked();
    let burnSubs = this.refs.burnSubs.isChecked();
    let subsTrack = get("subsTrack", this.state.subsTrack);
    let start = this.refs.start.getValue();
    let duration = this.refs.duration.getValue();
    let rawOpts = "";

    // Fixing.
    if (limit === "") limit = Empty;
    if (quality === "") quality = Empty;
    if (noAudio) audioBitrate = 0;
    if (audioBitrate === "") audioBitrate = Empty;
    if (start === "") start = Empty;
    if (duration === "") duration = Empty;

    // FIXME(Kagami): Validation.
    let newState = {
      mode, videoTrack, limit, quality,
      noAudio, audioTrack, audioBitrate,
      useEndTime, burnSubs, subsTrack, start, duration,
    };
    // FIXME(Kagami): Pass normalized values to makeRawOpts?
    rawOpts = this.makeRawOpts(newState).join(" ");
    newState.rawOpts = rawOpts;

    // Setting.
    this.setState(newState);
    // We don't use value property of TextField because it's very slow.
    // See: <https://github.com/callemall/material-ui/issues/1040>.
    this.refs.limit.setValue(limit);
    this.refs.quality.setValue(quality);
    this.refs.audioBitrate.setValue(audioBitrate);
    this.refs.start.setValue(start);
    this.refs.duration.setValue(duration);
    if (this.refs.rawOpts) this.refs.rawOpts.setValue(rawOpts);
  },
  handleUIModeClick: function() {
    this.setState({advanced: !this.state.advanced});
    this.handleUI();
  },
  handleEncodeClick: function() {
    const params = this.state.rawOpts.trim().split(/\s+/);
    this.props.onReady(params);
  },
  render: function() {
    const video = (
      <ClearFix style={styles.section}>

          <RadioButtonGroup
            ref="mode"
            name="mode"
            defaultSelected="limit"
            onChange={this.handleMode}
          >
            <RadioButton
              value="limit"
              label="Fit to limit"
            />
            <RadioButton
              value="bitrate"
              label="Custom bitrate"
            />
            <RadioButton
              value="constq"
              label="Constant quality"
            />
          </RadioButtonGroup>
          <SelectField
            value={this.state.videoTrack}
            hintText="Select video track"
            onChange={this.handleSelect.bind(null, "videoTrack")}
            menuItems={this.getItems("video")}
          />

            <SmallInput
              ref="limit"
              defaultValue={this.DEFAULT_LIMIT}
              floatingLabelText={this.getLimitLabel()}
              disabled={this.state.mode === "constq"}
              onBlur={this.handleEvent}
            />
            <SmallInput
              ref="quality"
              // NOTE(Kagami): This is not mandatory since we set it
              // `handleUI` anyway but omitting it here would cause a
              // noticeable flash.
              defaultValue={Empty}
              floatingLabelText="quality (4÷63)"
              onBlur={this.handleEvent}
            />

          <ShowHide show={this.state.advanced}>

              <SmallInput
                defaultValue={Empty}
                floatingLabelText="qmin (4÷63)"
              />
              <SmallInput
                defaultValue={Empty}
                floatingLabelText="qmax (4÷63)"
              />

          </ShowHide>

      </ClearFix>
    );
    const audio = (
      <ClearFix style={styles.section}>

          <Checkbox
            ref="noAudio"
            label="No audio"
            onCheck={this.handleNoAudio}
            disabled={!this.hasAudioTracks()}
            defaultChecked={!this.hasAudioTracks()}
          />
          <SelectField
            value={this.state.audioTrack}
            hintText="Select audio track"
            // Doesn't work for now, see:
            // <https://github.com/callemall/material-ui/issues/1294>.
            // disabled={this.state.noAudio}
            onChange={this.handleSelect.bind(null, "audioTrack")}
            menuItems={this.getItems("audio")}
          />

          <SmallInput
            ref="audioBitrate"
            defaultValue={this.DEFAULT_AUIDIO_BITRATE}
            floatingLabelText="bitrate (kbits)"
            disabled={this.state.noAudio}
            onBlur={this.handleEvent}
          />

      </ClearFix>
    );
    const other = (

        <ClearFix>

            <Checkbox
              ref="useEndTime"
              label="Specify end time"
              onCheck={this.handleEvent}
            />
            <Checkbox
              ref="burnSubs"
              label="Burn subtitles"
              onCheck={this.handleEvent}
              disabled={!this.hasSubsTracks()}
              defaultChecked={this.hasSubsTracks()}
            />
            <SelectField
              value={this.state.subsTrack}
              hintText="Subtitles track"
              // disabled={!this.state.burnSubs}
              onChange={this.handleSelect.bind(null, "subsTrack")}
              menuItems={this.getItems("subs")}
            />

              <SmallInput
                ref="start"
                defaultValue={Empty}
                floatingLabelText="start (time)"
                onBlur={this.handleEvent}
              />
              <SmallInput
                ref="duration"
                defaultValue={this.getDefaultDuration()}
                floatingLabelText={this.getDurationLabel()}
                onBlur={this.handleEvent}
              />

            <ShowHide show={this.state.advanced}>

                <SmallInput
                  defaultValue={getDefaultVideoThreads()}
                  floatingLabelText="threads (1÷8)"
                />
                <SmallInput
                  defaultValue="1"
                  floatingLabelText="speed (0÷5)"
                />

            </ShowHide>

        </ClearFix>
        <ShowHide show={this.state.advanced}>
          <TextField
            ref="rawOpts"
            floatingLabelText="Raw ffmpeg options"
            defaultValue={this.state.rawOpts}
            multiLine
            fullWidth
            onBlur={this.handleRawOpts}
          />
        </ShowHide>

    );
    return (
      <Paper style={styles.root}>

output video params

        {video}

audio

        {audio}

other

        {other}

          <RaisedButton
            style={styles.bigButton}
            primary
            label="start encoding"
            onClick={this.handleEncodeClick}
          />

        <FlatButton
          primary
          style={styles.uiMode}
          onClick={this.handleUIModeClick}
          label={this.state.advanced ? "basic" : "advanced"}
        />
      </Paper>
    );
  },
});
<commit_msg>Clear quality on mode change<commit_after>/**
 * Output video params widget.
 * @module webm/params
 */

import React from "react";
import {
  Paper, RadioButtonGroup, RadioButton, Checkbox,
  FlatButton, SelectField, ClearFix, TextField,
  RaisedButton, SmallInput,
} from "./theme";
import {ShowHide, has, getDefaultVideoThreads} from "./util";

const styles = {
  root: {
    position: "relative",
  },
  header: {
    paddingTop: 8,
    paddingLeft: 8,
    color: "#e0e0e0",
    fontWeight: 500,
    fontSize: "18px",
    textTransform: "uppercase",
  },
  section: {
    padding: "16px 24px",
    borderBottom: "1px solid #e0e0e0",
  },
  lastSection: {
    padding: "16px 24px 24px 24px",
  },
  left: {
    float: "left",
    width: "60%",
  },
  right: {
    float: "left",
    marginTop: -30,
  },
  smallSelect: {
    width: 230,
  },
  encode: {
    textAlign: "center",
    paddingBottom: 16,
  },
  bigButton: {
    width: 250,
  },
  uiMode: {
    width: 110,
    position: "absolute",
    bottom: 0,
    right: 0,
    borderRadius: 0,
  },
};

// XXX(Kagami): Singleton to satisfate `TextField.hasValue` and emulate
// filled text field because there is no other way to display floating
// label by default.
// TODO(Kagami): Request this feature.
const Empty = {
  toString: function() {
    return "";
  },
};

export default React.createClass({
  getInitialState: function() {
    return {};
  },
  componentDidMount: function() {
    this.handleUI();
    if (window.localStorage && localStorage.SKIP_PARAMS) {
      // XXX(Kagami): Workaround asynchronous setState. Not sure whether
      // it will always work, but this is debug-only anyway.
      setTimeout(this.handleEncodeClick);
    }
  },
  DEFAULT_LIMIT: 8,
  DEFAULT_QUALITY: 20,
  DEFAULT_AUIDIO_BITRATE: 64,
  getLimitLabel: function() {
    return this.state.mode === "limit" ? "limit (MiB)" : "bitrate (kbits)";
  },
  getDefaultDuration: function() {
    return window.localStorage && localStorage.DURATION || Empty;
  },
  getDurationLabel: function() {
    return this.state.useEndTime ? "end (time)" : "duration (time)";
  },
  getItems: function(type) {
    return this.props.info[type].map(track => {
      let text = `#${track.id} - ${track.codec}`;
      if (track.default) text += " (default)";
      return {payload: track.id, text};
    });
  },
  // NOTE(Kagami): We will always have video tracks because we checked
  // for them in Info component. Audio/subtitles tracks are not required
  // though.
  hasAudioTracks: function() {
    return !!this.props.info.audio.length;
  },
  hasSubsTracks: function() {
    return !!this.props.info.subs.length;
  },
  calcVideoBitrate: function({limit, audioBitrate}) {
    limit = +limit;
    audioBitrate = +audioBitrate;
    // FIXME(Kagami): Take start/end into account and check for zero
    // length/intersections/etc.
    const duration = this.props.info.duration;
    const limitKbits = limit * 8 * 1024;
    const vb = Math.floor(limitKbits / duration - audioBitrate);
    return vb > 0 ? vb : 1;
  },
  makeRawOpts: function(state) {
    // NOTE(Kagami): We accept state variables via arguments because
    // `setState` is asynchronous and values in `this.state` might be
    // outdated.
    // TODO(Kagami): scale, crop, custom filters, set/clear metadata.
    let opts = [];
    // This might be `Empty`.
    const limit = state.limit.toString();
    const quality = state.quality.toString();
    let vb = state.mode === "limit" ? this.calcVideoBitrate(state) : +limit;
    if (vb !== 0) vb += "k";
    const duration = state.duration.toString();

    // Input.
    opts.push("-i", this.props.source.safeName);
    if (duration !== "") {
      // FIXME(Kagami): Fix duration in case of `useEndTime`.
      // NOTE(Kagami): We always use `-t` in resulting command because
      // `-ss` before `-i` resets the timestamp, see:
      // <https://trac.ffmpeg.org/wiki/Seeking#Notes>.
      opts.push("-t", duration);
    }

    // Streams.

    // Video.
    // TODO(Kagami): We can improve quality a bit with "-speed 0 -g 9999".
    // Though this will slow down the encoding so we need to find the
    // best speed/quality compromise for in-browser use.
    opts.push("-c:v", "libvpx", "-speed", "1");
    opts.push("-auto-alt-ref", "1", "-lag-in-frames", "25");
    opts.push("-b:v", vb);
    if (quality !== "") opts.push("-crf", quality);

    // Audio.
    if (state.noAudio) {
      opts.push("-an");
    } else {
      opts.push("-c:a", "libopus", "-ac", "2");
      opts.push("-b:a", state.audioBitrate + "k");
    }

    // Subs.
    opts.push("-sn");

    return opts;
  },
  handleMode: function(e, mode) {
    // NOTE(Kagami): `radio.getSelectedValue` isn't always up-to-date,
    // so we need this function to get the actual value. See:
    // <https://github.com/callemall/material-ui/issues/295>.
    // Though we need this function anyway to fix some settings in the
    // moment of switch.
    let upd = {mode, quality: ""};
    const prevMode = this.state.mode;
    if (mode === "limit") {
      upd.limit = this.DEFAULT_LIMIT;
    } else if (mode === "bitrate") {
      if (prevMode === "limit") {
        upd.limit = this.calcVideoBitrate(this.state);
      } else if (prevMode === "constq") {
        upd.limit = "";
      }
    } else if (mode === "constq") {
      upd.limit = 0;
      upd.quality = this.DEFAULT_QUALITY;
    }
    this.handleUI(upd);
  },
  handleNoAudio: function(e, noAudio) {
    let audioBitrate = noAudio ? 0 : this.DEFAULT_AUIDIO_BITRATE;
    this.handleUI({audioBitrate});
  },
  // Helper to ignore passed event arguments.
  handleEvent: function() {
    this.handleUI();
  },
  handleSelect: function(name, e, index, payload) {
    let change = {};
    change[name] = payload.payload;
    this.handleUI(change);
  },
  handleRawOpts: function(e) {
    this.setState({rawOpts: e.target.value});
  },
  handleUI: function(preState) {
    preState = preState || {};
    function get(option, def) {
      return has(preState, option) ? preState[option] : def;
    }

    // Getting.
    let mode = get("mode", this.refs.mode.getSelectedValue());
    let videoTrack = get("videoTrack", this.state.videoTrack);
    let limit = get("limit", this.refs.limit.getValue());
    let quality = get("quality", this.refs.quality.getValue());
    let noAudio = this.refs.noAudio.isChecked();
    let audioTrack = get("audioTrack", this.state.audioTrack);
    let audioBitrate = get("audioBitrate", this.refs.audioBitrate.getValue());
    let useEndTime = this.refs.useEndTime.isChecked();
    let burnSubs = this.refs.burnSubs.isChecked();
    let subsTrack = get("subsTrack", this.state.subsTrack);
    let start = this.refs.start.getValue();
    let duration = this.refs.duration.getValue();
    let rawOpts = "";

    // Fixing.
    if (limit === "") limit = Empty;
    if (quality === "") quality = Empty;
    if (noAudio) audioBitrate = 0;
    if (audioBitrate === "") audioBitrate = Empty;
    if (start === "") start = Empty;
    if (duration === "") duration = Empty;

    // FIXME(Kagami): Validation.
    let newState = {
      mode, videoTrack, limit, quality,
      noAudio, audioTrack, audioBitrate,
      useEndTime, burnSubs, subsTrack, start, duration,
    };
    // FIXME(Kagami): Pass normalized values to makeRawOpts?
    rawOpts = this.makeRawOpts(newState).join(" ");
    newState.rawOpts = rawOpts;

    // Setting.
    this.setState(newState);
    // We don't use value property of TextField because it's very slow.
    // See: <https://github.com/callemall/material-ui/issues/1040>.
    this.refs.limit.setValue(limit);
    this.refs.quality.setValue(quality);
    this.refs.audioBitrate.setValue(audioBitrate);
    this.refs.start.setValue(start);
    this.refs.duration.setValue(duration);
    if (this.refs.rawOpts) this.refs.rawOpts.setValue(rawOpts);
  },
  handleUIModeClick: function() {
    this.setState({advanced: !this.state.advanced});
    this.handleUI();
  },
  handleEncodeClick: function() {
    const params = this.state.rawOpts.trim().split(/\s+/);
    this.props.onReady(params);
  },
  render: function() {
    const video = (
      <ClearFix style={styles.section}>

          <RadioButtonGroup
            ref="mode"
            name="mode"
            defaultSelected="limit"
            onChange={this.handleMode}
          >
            <RadioButton
              value="limit"
              label="Fit to limit"
            />
            <RadioButton
              value="bitrate"
              label="Custom bitrate"
            />
            <RadioButton
              value="constq"
              label="Constant quality"
            />
          </RadioButtonGroup>
          <SelectField
            value={this.state.videoTrack}
            hintText="Select video track"
            onChange={this.handleSelect.bind(null, "videoTrack")}
            menuItems={this.getItems("video")}
          />

            <SmallInput
              ref="limit"
              defaultValue={this.DEFAULT_LIMIT}
              floatingLabelText={this.getLimitLabel()}
              disabled={this.state.mode === "constq"}
              onBlur={this.handleEvent}
            />
            <SmallInput
              ref="quality"
              // NOTE(Kagami): This is not mandatory since we set it
              // `handleUI` anyway but omitting it here would cause a
              // noticeable flash.
              defaultValue={Empty}
              floatingLabelText="quality (4÷63)"
              onBlur={this.handleEvent}
            />

          <ShowHide show={this.state.advanced}>

              <SmallInput
                defaultValue={Empty}
                floatingLabelText="qmin (4÷63)"
              />
              <SmallInput
                defaultValue={Empty}
                floatingLabelText="qmax (4÷63)"
              />

          </ShowHide>

      </ClearFix>
    );
    const audio = (
      <ClearFix style={styles.section}>

          <Checkbox
            ref="noAudio"
            label="No audio"
            onCheck={this.handleNoAudio}
            disabled={!this.hasAudioTracks()}
            defaultChecked={!this.hasAudioTracks()}
          />
          <SelectField
            value={this.state.audioTrack}
            hintText="Select audio track"
            // Doesn't work for now, see:
            // <https://github.com/callemall/material-ui/issues/1294>.
            // disabled={this.state.noAudio}
            onChange={this.handleSelect.bind(null, "audioTrack")}
            menuItems={this.getItems("audio")}
          />

          <SmallInput
            ref="audioBitrate"
            defaultValue={this.DEFAULT_AUIDIO_BITRATE}
            floatingLabelText="bitrate (kbits)"
            disabled={this.state.noAudio}
            onBlur={this.handleEvent}
          />

      </ClearFix>
    );
    const other = (

        <ClearFix>

            <Checkbox
              ref="useEndTime"
              label="Specify end time"
              onCheck={this.handleEvent}
            />
            <Checkbox
              ref="burnSubs"
              label="Burn subtitles"
              onCheck={this.handleEvent}
              disabled={!this.hasSubsTracks()}
              defaultChecked={this.hasSubsTracks()}
            />
            <SelectField
              value={this.state.subsTrack}
              hintText="Subtitles track"
              // disabled={!this.state.burnSubs}
              onChange={this.handleSelect.bind(null, "subsTrack")}
              menuItems={this.getItems("subs")}
            />

              <SmallInput
                ref="start"
                defaultValue={Empty}
                floatingLabelText="start (time)"
                onBlur={this.handleEvent}
              />
              <SmallInput
                ref="duration"
                defaultValue={this.getDefaultDuration()}
                floatingLabelText={this.getDurationLabel()}
                onBlur={this.handleEvent}
              />

            <ShowHide show={this.state.advanced}>

                <SmallInput
                  defaultValue={getDefaultVideoThreads()}
                  floatingLabelText="threads (1÷8)"
                />
                <SmallInput
                  defaultValue="1"
                  floatingLabelText="speed (0÷5)"
                />

            </ShowHide>

        </ClearFix>
        <ShowHide show={this.state.advanced}>
          <TextField
            ref="rawOpts"
            floatingLabelText="Raw ffmpeg options"
            defaultValue={this.state.rawOpts}
            multiLine
            fullWidth
            onBlur={this.handleRawOpts}
          />
        </ShowHide>

    );
    return (
      <Paper style={styles.root}>

output video params

        {video}

audio

        {audio}

other

        {other}

          <RaisedButton
            style={styles.bigButton}
            primary
            label="start encoding"
            onClick={this.handleEncodeClick}
          />

        <FlatButton
          primary
          style={styles.uiMode}
          onClick={this.handleUIModeClick}
          label={this.state.advanced ? "basic" : "advanced"}
        />
      </Paper>
    );
  },
});
<|endoftext|>


<commit_before>/**
 * Created by huangxinghui on 2015/5/15.
 */

var $ = require('jquery');
var affix = 'bean.';

module.exports = plugin;

function plugin(widgetName, widgetClass) {
  // add widgetName to widget prototype
  widgetClass.prototype.widgetName = widgetName;

  function Plugin(option) {
    var isMethodCall = typeof option === "string",
        args = Array.prototype.slice.call(arguments, 1),
        returnValue = this;

    if (isMethodCall) {
      this.each(function() {
        var $this = $(this);
        var data = $this.data(affix + widgetName);

        if (!data) $this.data(affix + widgetName, data = new widgetClass(this));
        returnValue = data[option].apply(data, args);
      });
    } else {
      this.each(function() {
        var $this = $(this);
        var options = typeof option == 'object' && option;

        $this.data(affix + widgetName, new widgetClass(this, options));
      });
    }

    return returnValue;
  }

  var old = $.fn[widgetName];

  $.fn[widgetName] = Plugin;

  // Widget NO CONFLICT
  // ====================

  $.fn[widgetName].noConflict = function() {
    $.fn[widgetName] = old;
    return this;
  };
}<commit_msg>change widget data<commit_after>/**
 * Created by huangxinghui on 2015/5/15.
 */

var $ = require('jquery');
var Bean = require('./core');

module.exports = plugin;

function plugin(widgetName, widgetClass) {
  // add widgetName to widget prototype
  widgetClass.prototype.widgetName = widgetName;

  function Plugin(option) {
    var isMethodCall = typeof option === "string",
        args = Array.prototype.slice.call(arguments, 1),
        returnValue = this;

    if (isMethodCall) {
      this.each(function() {
        var $this = $(this);
        var data = $this.data(Bean.ns + widgetName);

        if (!data) $this.data(Bean.ns + widgetName, data = new widgetClass(this));
        returnValue = data[option].apply(data, args);
      });
    } else {
      this.each(function() {
        var $this = $(this);
        var options = typeof option == 'object' && option;

        $this.data(Bean.ns + widgetName, new widgetClass(this, options));
      });
    }

    return returnValue;
  }

  var old = $.fn[widgetName];

  $.fn[widgetName] = Plugin;

  // Widget NO CONFLICT
  // ====================

  $.fn[widgetName].noConflict = function() {
    $.fn[widgetName] = old;
    return this;
  };
}
<|endoftext|>


<commit_before>const WebSocket = require('uws');
const Constants = require('../../util/Constants');
const SecretKey = require('./util/SecretKey');
const EventEmitter = require('events').EventEmitter;

/**
 * Represents a Voice Connection's WebSocket
 * @extends {EventEmitter}
 * @private
 */
class VoiceWebSocket extends EventEmitter {
  constructor(voiceConnection) {
    super();

    /**
     * The Voice Connection that this WebSocket serves
     * @type {VoiceConnection}
     */
    this.voiceConnection = voiceConnection;

    /**
     * How many connection attempts have been made
     * @type {number}
     */
    this.attempts = 0;

    this.connect();
    this.dead = false;
    this.voiceConnection.on('closing', this.shutdown.bind(this));
  }

  shutdown() {
    this.dead = true;
    this.reset();
  }

  /**
   * The client of this voice websocket
   * @type {Client}
   * @readonly
   */
  get client() {
    return this.voiceConnection.voiceManager.client;
  }

  /**
   * Resets the current WebSocket
   */
  reset() {
    if (this.ws) {
      if (this.ws.readyState !== WebSocket.CLOSED) this.ws.close();
      this.ws = null;
    }
    this.clearHeartbeat();
  }

  /**
   * Starts connecting to the Voice WebSocket Server.
   */
  connect() {
    if (this.dead) return;
    if (this.ws) this.reset();
    if (this.attempts > 5) {
      this.emit('error', new Error(`Too many connection attempts (${this.attempts}).`));
      return;
    }

    this.attempts++;

    /**
     * The actual WebSocket used to connect to the Voice WebSocket Server.
     * @type {WebSocket}
     */
    this.ws = new WebSocket(`wss://${this.voiceConnection.authentication.endpoint}`);
    this.ws.onopen = this.onOpen.bind(this);
    this.ws.onmessage = this.onMessage.bind(this);
    this.ws.onclose = this.onClose.bind(this);
    this.ws.onerror = this.onError.bind(this);
  }

  /**
   * Sends data to the WebSocket if it is open.
   * @param {string} data the data to send to the WebSocket
   * @returns {Promise<string>}
   */
  send(data) {
    return new Promise((resolve, reject) => {
      if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
        throw new Error(`Voice websocket not open to send ${data}.`);
      }
      this.ws.send(data, null, error => {
        if (error) reject(error); else resolve(data);
      });
    });
  }

  /**
   * JSON.stringify's a packet and then sends it to the WebSocket Server.
   * @param {Object} packet the packet to send
   * @returns {Promise<string>}
   */
  sendPacket(packet) {
    try {
      packet = JSON.stringify(packet);
    } catch (error) {
      return Promise.reject(error);
    }
    return this.send(packet);
  }

  /**
   * Called whenever the WebSocket opens
   */
  onOpen() {
    this.sendPacket({
      op: Constants.OPCodes.DISPATCH,
      d: {
        server_id: this.voiceConnection.channel.guild.id,
        user_id: this.client.user.id,
        token: this.voiceConnection.authentication.token,
        session_id: this.voiceConnection.authentication.session_id,
      },
    }).catch(() => {
      this.emit('error', new Error('Tried to send join packet, but the WebSocket is not open.'));
    });
  }

  /**
   * Called whenever a message is received from the WebSocket
   * @param {MessageEvent} event the message event that was received
   * @returns {void}
   */
  onMessage(event) {
    try {
      return this.onPacket(JSON.parse(event.data));
    } catch (error) {
      return this.onError(error);
    }
  }

  /**
   * Called whenever the connection to the WebSocket Server is lost
   */
  onClose() {
    if (!this.dead) this.client.setTimeout(this.connect.bind(this), this.attempts * 1000);
  }

  /**
   * Called whenever an error occurs with the WebSocket.
   * @param {Error} error the error that occurred
   */
  onError(error) {
    this.emit('error', error);
  }

  /**
   * Called whenever a valid packet is received from the WebSocket
   * @param {Object} packet the received packet
   */
  onPacket(packet) {
    switch (packet.op) {
      case Constants.VoiceOPCodes.READY:
        this.setHeartbeat(packet.d.heartbeat_interval);
        /**
         * Emitted once the voice websocket receives the ready packet
         * @param {Object} packet the received packet
         * @event VoiceWebSocket#ready
         */
        this.emit('ready', packet.d);
        break;
      case Constants.VoiceOPCodes.SESSION_DESCRIPTION:
        /**
         * Emitted once the Voice Websocket receives a description of this voice session
         * @param {string} encryptionMode the type of encryption being used
         * @param {SecretKey} secretKey the secret key used for encryption
         * @event VoiceWebSocket#sessionDescription
         */
        this.emit('sessionDescription', packet.d.mode, new SecretKey(packet.d.secret_key));
        break;
      case Constants.VoiceOPCodes.SPEAKING:
        /**
         * Emitted whenever a speaking packet is received
         * @param {Object} data
         * @event VoiceWebSocket#speaking
         */
        this.emit('speaking', packet.d);
        break;
      default:
        /**
         * Emitted when an unhandled packet is received
         * @param {Object} packet
         * @event VoiceWebSocket#unknownPacket
         */
        this.emit('unknownPacket', packet);
        break;
    }
  }

  /**
   * Sets an interval at which to send a heartbeat packet to the WebSocket
   * @param {number} interval the interval at which to send a heartbeat packet
   */
  setHeartbeat(interval) {
    if (!interval || isNaN(interval)) {
      this.onError(new Error('Tried to set voice heartbeat but no valid interval was specified.'));
      return;
    }
    if (this.heartbeatInterval) {
      /**
       * Emitted whenver the voice websocket encounters a non-fatal error
       * @param {string} warn the warning
       * @event VoiceWebSocket#warn
       */
      this.emit('warn', 'A voice heartbeat interval is being overwritten');
      clearInterval(this.heartbeatInterval);
    }
    this.heartbeatInterval = this.client.setInterval(this.sendHeartbeat.bind(this), interval);
  }

  /**
   * Clears a heartbeat interval, if one exists
   */
  clearHeartbeat() {
    if (!this.heartbeatInterval) {
      this.emit('warn', 'Tried to clear a heartbeat interval that does not exist');
      return;
    }
    clearInterval(this.heartbeatInterval);
    this.heartbeatInterval = null;
  }

  /**
   * Sends a heartbeat packet
   */
  sendHeartbeat() {
    this.sendPacket({ op: Constants.VoiceOPCodes.HEARTBEAT, d: null }).catch(() => {
      this.emit('warn', 'Tried to send heartbeat, but connection is not open');
      this.clearHeartbeat();
    });
  }
}

module.exports = VoiceWebSocket;
<commit_msg>fix voice ws (#922)<commit_after>const WebSocket = require('uws');
const Constants = require('../../util/Constants');
const SecretKey = require('./util/SecretKey');
const EventEmitter = require('events').EventEmitter;

/**
 * Represents a Voice Connection's WebSocket
 * @extends {EventEmitter}
 * @private
 */
class VoiceWebSocket extends EventEmitter {
  constructor(voiceConnection) {
    super();

    /**
     * The Voice Connection that this WebSocket serves
     * @type {VoiceConnection}
     */
    this.voiceConnection = voiceConnection;

    /**
     * How many connection attempts have been made
     * @type {number}
     */
    this.attempts = 0;

    this.connect();
    this.dead = false;
    this.voiceConnection.on('closing', this.shutdown.bind(this));
  }

  shutdown() {
    this.dead = true;
    this.reset();
  }

  /**
   * The client of this voice websocket
   * @type {Client}
   * @readonly
   */
  get client() {
    return this.voiceConnection.voiceManager.client;
  }

  /**
   * Resets the current WebSocket
   */
  reset() {
    if (this.ws) {
      if (this.ws.readyState !== WebSocket.CLOSED) this.ws.close();
      this.ws = null;
    }
    this.clearHeartbeat();
  }

  /**
   * Starts connecting to the Voice WebSocket Server.
   */
  connect() {
    if (this.dead) return;
    if (this.ws) this.reset();
    if (this.attempts > 5) {
      this.emit('error', new Error(`Too many connection attempts (${this.attempts}).`));
      return;
    }

    this.attempts++;

    /**
     * The actual WebSocket used to connect to the Voice WebSocket Server.
     * @type {WebSocket}
     */
    this.ws = new WebSocket(`wss://${this.voiceConnection.authentication.endpoint}`);
    if (typeof window !== 'undefined') {
      this.ws.onopen = this.onOpen.bind(this);
      this.ws.onerror = this.onError.bind(this);
    } else {
      this.ws.on('open', this.onOpen.bind(this));
      this.ws.on('error', this.onError.bind(this));
    }
    this.ws.onmessage = this.onMessage.bind(this);
    this.ws.onclose = this.onClose.bind(this);
  }

  /**
   * Sends data to the WebSocket if it is open.
   * @param {string} data the data to send to the WebSocket
   * @returns {Promise<string>}
   */
  send(data) {
    return new Promise((resolve, reject) => {
      if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
        throw new Error(`Voice websocket not open to send ${data}.`);
      }
      this.ws.send(data, null, error => {
        if (error) reject(error); else resolve(data);
      });
    });
  }

  /**
   * JSON.stringify's a packet and then sends it to the WebSocket Server.
   * @param {Object} packet the packet to send
   * @returns {Promise<string>}
   */
  sendPacket(packet) {
    try {
      packet = JSON.stringify(packet);
    } catch (error) {
      return Promise.reject(error);
    }
    return this.send(packet);
  }

  /**
   * Called whenever the WebSocket opens
   */
  onOpen() {
    this.sendPacket({
      op: Constants.OPCodes.DISPATCH,
      d: {
        server_id: this.voiceConnection.channel.guild.id,
        user_id: this.client.user.id,
        token: this.voiceConnection.authentication.token,
        session_id: this.voiceConnection.authentication.session_id,
      },
    }).catch(() => {
      this.emit('error', new Error('Tried to send join packet, but the WebSocket is not open.'));
    });
  }

  /**
   * Called whenever a message is received from the WebSocket
   * @param {MessageEvent} event the message event that was received
   * @returns {void}
   */
  onMessage(event) {
    try {
      return this.onPacket(JSON.parse(event.data));
    } catch (error) {
      return this.onError(error);
    }
  }

  /**
   * Called whenever the connection to the WebSocket Server is lost
   */
  onClose() {
    if (!this.dead) this.client.setTimeout(this.connect.bind(this), this.attempts * 1000);
  }

  /**
   * Called whenever an error occurs with the WebSocket.
   * @param {Error} error the error that occurred
   */
  onError(error) {
    this.emit('error', error);
  }

  /**
   * Called whenever a valid packet is received from the WebSocket
   * @param {Object} packet the received packet
   */
  onPacket(packet) {
    switch (packet.op) {
      case Constants.VoiceOPCodes.READY:
        this.setHeartbeat(packet.d.heartbeat_interval);
        /**
         * Emitted once the voice websocket receives the ready packet
         * @param {Object} packet the received packet
         * @event VoiceWebSocket#ready
         */
        this.emit('ready', packet.d);
        break;
      case Constants.VoiceOPCodes.SESSION_DESCRIPTION:
        /**
         * Emitted once the Voice Websocket receives a description of this voice session
         * @param {string} encryptionMode the type of encryption being used
         * @param {SecretKey} secretKey the secret key used for encryption
         * @event VoiceWebSocket#sessionDescription
         */
        this.emit('sessionDescription', packet.d.mode, new SecretKey(packet.d.secret_key));
        break;
      case Constants.VoiceOPCodes.SPEAKING:
        /**
         * Emitted whenever a speaking packet is received
         * @param {Object} data
         * @event VoiceWebSocket#speaking
         */
        this.emit('speaking', packet.d);
        break;
      default:
        /**
         * Emitted when an unhandled packet is received
         * @param {Object} packet
         * @event VoiceWebSocket#unknownPacket
         */
        this.emit('unknownPacket', packet);
        break;
    }
  }

  /**
   * Sets an interval at which to send a heartbeat packet to the WebSocket
   * @param {number} interval the interval at which to send a heartbeat packet
   */
  setHeartbeat(interval) {
    if (!interval || isNaN(interval)) {
      this.onError(new Error('Tried to set voice heartbeat but no valid interval was specified.'));
      return;
    }
    if (this.heartbeatInterval) {
      /**
       * Emitted whenver the voice websocket encounters a non-fatal error
       * @param {string} warn the warning
       * @event VoiceWebSocket#warn
       */
      this.emit('warn', 'A voice heartbeat interval is being overwritten');
      clearInterval(this.heartbeatInterval);
    }
    this.heartbeatInterval = this.client.setInterval(this.sendHeartbeat.bind(this), interval);
  }

  /**
   * Clears a heartbeat interval, if one exists
   */
  clearHeartbeat() {
    if (!this.heartbeatInterval) {
      this.emit('warn', 'Tried to clear a heartbeat interval that does not exist');
      return;
    }
    clearInterval(this.heartbeatInterval);
    this.heartbeatInterval = null;
  }

  /**
   * Sends a heartbeat packet
   */
  sendHeartbeat() {
    this.sendPacket({ op: Constants.VoiceOPCodes.HEARTBEAT, d: null }).catch(() => {
      this.emit('warn', 'Tried to send heartbeat, but connection is not open');
      this.clearHeartbeat();
    });
  }
}

module.exports = VoiceWebSocket;
<|endoftext|>


<commit_before>import fs from '@skpm/fs';
import path from '@skpm/path';
import config from './config';
import { safeJSONParser } from './utils';

const fileManager = NSFileManager.defaultManager();

/**
 * search manifest file recursively
 * @param   {String} basePath   search path
 * @return  {Object}
 */
function searchManifests(basePath = config.paths.plugin, result = {}) {
  const files = fileManager.contentsOfDirectoryAtPath_error(basePath, null);

  Array.prototype.forEach.call(files, (item) => {
    const file = String(item);
    const filePath = path.join(basePath, file);
    const expectedPath = path.join(filePath, config.paths.manifest);

    if (/\.sketchplugin/.test(file) && fs.existsSync(expectedPath)) {
      result[file.replace(/\.\w+$/, '')] = expectedPath;
    } else if (fs.statSync(filePath).isDirectory()) {
      searchManifests(filePath, result);
    }
  });

  return result;
}

/**
 * find menu path in manifest for specific command
 * @param   {Array}   menu      plugin menu configuration of manifest
 * @param   {Object}  command   command configuration of manifest
 * @return  {Array}
 */
function findMenuPath(menu, command) {
  const result = [];

  for (let i = 0; i < menu.length; i += 1) {
    if (menu[i].items) {
      // find sub path if it has items
      const subPath = findMenuPath(menu[i].items, command);

      if (subPath.length) {
        // break if find target item
        result.push(menu[i].title, ...subPath);
        break;
      }
    } else if (menu[i].trim() === command.identifier.trim()) {
      // save command name if find target item
      result.push(command.name);
      break;
    }
  }

  return result;
}

/**
 * sort shortcut keys, eg: "(ctrl) (option) (shift) (cmd) key"
 * @param   {String}  shortcut    original shortcut
 * @return  {String}
 */
function sortShortcut(shortcut) {
  let result = shortcut;

  if (!/^((ctrl|control) )?((alt|option) )?(shift )?((cmd|command) )?\S*$/.test(shortcut)) {
    const fragments = shortcut.split(' ');
    const seats = {
      ctrl: 1,
      control: 1,
      option: 2,
      alt: 2,
      shift: 3,
      cmd: 4,
      command: 4,
    };

    result = fragments.reduce((sorted, item) => {
      sorted[(seats[item] || 5) - 1] = `${item} `;

      return sorted;
    }, []).join('').replace(/ $/, '');
  }

  return result;
}

const pluginHandler = {
  get() {
    const result = [];

    Object.keys(this.manifests).forEach((name) => {
      const manifest = this.getMainifest(name);
      const menuStr = JSON.stringify((manifest.menu && manifest.menu.items) || '');

      result.push({
        fsName: name,
        name: manifest.name,
        identifier: manifest.identifier,
        commands: manifest.commands.filter(command => Boolean(command.identifier)).map((command) => {
          return {
            // generate menu path
            name: menuStr.indexOf(command.identifier) > -1 ?
              findMenuPath(manifest.menu.items, command).join(' → ') :
              command.name,
            shortcut: sortShortcut(command.shortcut),
            identifier: command.identifier,
          };
        }),
      });
    });

    return result;
  },
  getMainifest(plugin) {
    const mainfestPath = this.manifests[plugin];
    let result = {
      name: plugin,
      identifier: config.identifiers.nonexistent,
      commands: [],
    };

    if (mainfestPath) {
      const content = safeJSONParser(fs.readFileSync(mainfestPath, 'utf8'));

      result = content || {
        name: plugin,
        identifier: config.identifiers.parseError,
        commands: [],
      };
    }

    return result;
  },
  updateManifest(name, content) {
    const data = typeof content === 'string' ? content : JSON.stringify(content, null, 2);

    if (this.manifests[name]) {
      fs.writeFileSync(this.manifests[name], data);
    }
  },
  updateShortcut(name, replacement) {
    const content = this.getMainifest(name);

    for (let i = 0; i < content.commands.length; i += 1) {
      // search target command
      if (content.commands[i].identifier === replacement.identifier) {
        if (replacement.shortcut) {
          // update shortcut
          content.commands[i].shortcut = replacement.shortcut;
        } else {
          // remove shortcut
          delete content.commands[i].shortcut;
        }

        break;
      }
    }

    // write changes to mainfest
    this.updateManifest(name, content);
  },
};

// init manifest data when read manifests property
Object.defineProperty(pluginHandler, 'manifests', {
  get() {
    return this.__manifests || (this.__manifests = searchManifests());
  },
});

export default pluginHandler;
<commit_msg>perf: add cache system for plugin handler<commit_after>import fs from '@skpm/fs';
import path from '@skpm/path';
import config from './config';
import { safeJSONParser } from './utils';

const fileManager = NSFileManager.defaultManager();

/**
 * search manifest file recursively
 * @param   {String} basePath   search path
 * @return  {Object}
 */
function searchManifests(basePath = config.paths.plugin, result = {}) {
  const files = fileManager.contentsOfDirectoryAtPath_error(basePath, null);

  Array.prototype.forEach.call(files, (item) => {
    const file = String(item);
    const filePath = path.join(basePath, file);
    const expectedPath = path.join(filePath, config.paths.manifest);

    if (/\.sketchplugin/.test(file) && fs.existsSync(expectedPath)) {
      result[file.replace(/\.\w+$/, '')] = expectedPath;
    } else if (fs.statSync(filePath).isDirectory()) {
      searchManifests(filePath, result);
    }
  });

  return result;
}

/**
 * find menu path in manifest for specific command
 * @param   {Array}   menu      plugin menu configuration of manifest
 * @param   {Object}  command   command configuration of manifest
 * @return  {Array}
 */
function findMenuPath(menu, command) {
  const result = [];

  for (let i = 0; i < menu.length; i += 1) {
    if (menu[i].items) {
      // find sub path if it has items
      const subPath = findMenuPath(menu[i].items, command);

      if (subPath.length) {
        // break if find target item
        result.push(menu[i].title, ...subPath);
        break;
      }
    } else if (menu[i].trim() === command.identifier.trim()) {
      // save command name if find target item
      result.push(command.name);
      break;
    }
  }

  return result;
}

/**
 * sort shortcut keys, eg: "(ctrl) (option) (shift) (cmd) key"
 * @param   {String}  shortcut    original shortcut
 * @return  {String}
 */
function sortShortcut(shortcut) {
  let result = shortcut;

  if (!/^((ctrl|control) )?((alt|option) )?(shift )?((cmd|command) )?\S*$/.test(shortcut)) {
    const fragments = shortcut.split(' ');
    const seats = {
      ctrl: 1,
      control: 1,
      option: 2,
      alt: 2,
      shift: 3,
      cmd: 4,
      command: 4,
    };

    result = fragments.reduce((sorted, item) => {
      sorted[(seats[item] || 5) - 1] = `${item} `;

      return sorted;
    }, []).join('').replace(/ $/, '');
  }

  return result;
}

const pluginHandler = {
  manifests: {},
  get() {
    const result = [];

    Object.keys(this.paths).forEach((name) => {
      const manifest = this.getMainifest(name);
      const menuStr = JSON.stringify((manifest.menu && manifest.menu.items) || '');

      result.push({
        fsName: name,
        name: manifest.name,
        identifier: manifest.identifier,
        commands: manifest.commands.filter(command => Boolean(command.identifier)).map((command) => {
          return {
            // generate menu path
            name: menuStr.indexOf(command.identifier) > -1 ?
              findMenuPath(manifest.menu.items, command).join(' → ') :
              command.name,
            shortcut: sortShortcut(command.shortcut),
            identifier: command.identifier,
          };
        }),
      });
    });

    return result;
  },
  getMainifest(plugin) {
    let result;

    if (this.manifests[plugin]) {
      // read cache firstly
      result = this.manifests[plugin];
    } else if (this.paths[plugin]) {
      // read manifest file
      const content = safeJSONParser(fs.readFileSync(this.paths[plugin], 'utf8'));

      result = this.manifests[plugin] = content || {
        name: plugin,
        identifier: config.identifiers.parseError,
        commands: [],
      };
    } else {
      // report not found
      result = this.manifests[plugin] = {
        name: plugin,
        identifier: config.identifiers.nonexistent,
        commands: [],
      };
    }

    return result;
  },
  updateManifest(name, content) {
    const data = typeof content === 'string' ? content : JSON.stringify(content, null, 2);

    if (this.paths[name]) {
      // update cache
      this.manifests[name] = data;
      // update file
      fs.writeFileSync(this.paths[name], data);
    }
  },
  updateShortcut(name, replacement) {
    const content = this.getMainifest(name);

    for (let i = 0; i < content.commands.length; i += 1) {
      // search target command
      if (content.commands[i].identifier === replacement.identifier) {
        if (replacement.shortcut) {
          // update shortcut
          content.commands[i].shortcut = replacement.shortcut;
        } else {
          // remove shortcut
          delete content.commands[i].shortcut;
        }

        break;
      }
    }

    // write changes to manifest
    this.updateManifest(name, content);
  },
};

// init manifest file paths when read paths property
Object.defineProperty(pluginHandler, 'paths', {
  get() {
    return this.__paths || (this.__paths = searchManifests());
  },
});

export default pluginHandler;
<|endoftext|>