<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var collectParallel = require('collect-parallel/array');
var CountedReadySignal = require('ready-signal/counted');
var net = require('net');
var timers = require('timers');
var util = require('util');

var allocCluster = require('./lib/test-cluster.js');
var CollapsedAssert = require('./lib/collapsed-assert.js');

/* eslint-disable no-multi-spaces */
var PERIOD           = 100;
var REQUEST_TIMEOUT  = 200;
var COOL_OFF_PERIODS = 10;
var CLUSTER_SIZE     = 10;
var CHURN_FACTOR     = 0.5;
var K_VALUE          = 5;
var SERVICE_SIZE     = 10;

function fuzzedPeriods(N) {
    return 1.05 * N * PERIOD;
}

allocCluster.test('peer churn', {
    size: CLUSTER_SIZE,
    noBob: true,
    noSteve: true,
    noTCollector: true,

    remoteConfig: {
        'kValue.services': {
            'lucy': K_VALUE
        },
        'peerConnecter.period': PERIOD,
        'peerPruner.period': PERIOD,
        'peerReaper.period': PERIOD
    },

    whitelist: [

        // XXX do we care about the rare:
        // AUTOBAHN INFO: ignoring outresponse.send on a closed connection ~ { responseId: 5,

        ['info', 'reaping dead peers'],
        ['info', 'reaping dead peer'],
        ['info', 'draining peer'],

        ['error', 'Failed to register to hyperbahn for remote'],

        ['info', 'Peer drained and closed due to unadvertisement'],
        ['info', 'canceling peer drain'],
        ['info', 'draining pruned peer'],
        ['info', 'pruning peers'],
        ['info', 'stopping peer drain'],

        // XXX flappy logs but no harm
        ['warn', 'error while forwarding'],
        ['info', 'resetting connection']

    ]

}, function t(cluster, assert) {
    var serverOpts = {
        serviceName: 'lucy',
        registerEvery: 50
    };

    var clientOpts = {
        serviceName: 'kathy',
        registerEvery: 50
    };

    var client = cluster.createRemote(clientOpts, clientReady);
    var first = null;
    var second = null;
    var turnips = null;

    function clientReady() {
        assert.comment('- clientReady');
        checkAllLogs(cluster, assert, checkConnectingLog);
        first = createRemotes(cluster, SERVICE_SIZE, serverOpts, gotFirstRound);
    }

    function gotFirstRound() {
        assert.comment('- gotFirstRound');
        checkAllLogs(cluster, assert, checkConnectingLog);
        checkExitsTo(cluster, 'lucy', first, 'the first peers', assert);
        checkRequestsTo('lucy', first, 'the first peers', client.clientChannel, assert, setupSecondRound);
    }

    function setupSecondRound() {
        assert.comment('- setupSecondRound');
        checkNoLogs('first phase sends', cluster, assert);
        second = createRemotes(cluster, Math.round(CHURN_FACTOR * first.length), serverOpts, gotSecondRound);
    }

    function gotSecondRound() {
        assert.comment('- gotSecondRound');
        checkAllLogs(cluster, assert, checkConnectingLog);
        checkExitsTo(cluster, 'lucy', second, 'the second peers', assert);
        checkRequestsTo('lucy', first.concat(second), 'either generation', client.clientChannel, assert, thenShutdownFirstRound);
    }

    function thenShutdownFirstRound() {
        assert.comment('- thenShutdownFirstRound');
        collectParallel(first, function each(remote, i, done) {
            // half of them do an unad then destroy, the other half just go away
            if (i % 2 === 0) {
                remote.doUnregister(function unreged(err) {
                    assert.ifError(err, 'expected no unregister error');
                    remote.destroy(done);
                });
            } else {
                remote.destroy(done);
            }
        }, thenWaitForReaper);
    }

    function thenWaitForReaper() {
        assert.comment('- thenWaitForReaper');

        checkAllLogs(cluster, assert, function checkEachLog(record) {
            assert.ok([
                // happens for destructive shutdown
                'Failed to register to hyperbahn for remote',
                'expected registration failure during destruction',
                'resetting connection',

                // these happen due to the unads
                'Peer drained and closed due to unadvertisement',
                'canceling peer drain',
                'draining peer',
                'implementing affinity change',
                'stopping peer drain',
                'pruning peers'
            ].indexOf(record.msg) >= 0, 'expected reaping logs');
        });

        timers.setTimeout(thenTurnip, fuzzedPeriods(COOL_OFF_PERIODS));
    }

    function thenTurnip() {
        assert.comment('- thenTurnip');

        // checkNoLogs('reap', cluster, assert);

        checkAllLogs(cluster, assert, function checkEachLog(record) {
            assert.ok([
                // these happen more for the peers that didn't unad above
                'connecting peers',
                'resetting connection',
                'reaping dead peers',
                'reaping dead peer',
                'draining peer',
                'pruning peers'
            ].indexOf(record.msg) >= 0, 'expected peer churn logs');
        });

        turnips = createTurnips(first, thenWaitAndSee);
    }

    function thenWaitAndSee() {
        assert.comment('- thenWaitAndSee');

        checkNoLogs('turnip', cluster, assert);

        timers.setTimeout(function tendTurnips() {
            checkNoLogs('turnip tending', cluster, assert);
            checkTurnips();
            sendAfterChurn();
        }, fuzzedPeriods(COOL_OFF_PERIODS));
    }

    function checkTurnips() {
        var logs = turnips.takeConnLogs();
        for (var i = 0; i < logs.length; ++i) {
            var log = logs[i];
            assert.equal(log.length, 0, util.format(
                'expected no connections to turnip[%s] (%s)',
                i, turnips[i].remote.channel.hostPort
            ));
        }
    }

    function sendAfterChurn() {
        assert.comment('- sendAfterChurn');
        checkRequestsTo('lucy', second, 'the second peers', client.clientChannel, assert, thenDestroySecondRound);
    }

    function thenDestroySecondRound() {
        assert.comment('- thenDestroySecondRound');

        // XXX: these don't always happen, but when they do the client retries around them
        checkAllLogs(cluster, assert, function checkEachLog(record) {
            if (record.msg === 'error while forwarding') {
                var fields = record._logData.fields;
                assert.equal(fields.error.type, 'tchannel.connection.reset',
                    'expected connection reset while forwarding error');
            } else {
                assert.equal(record.msg, 'resetting connection',
                    'expected resetting connection log');
            }
        });

        checkTurnips();

        destroyAll(second.concat(turnips).concat([client]), finish);
    }

    function finish() {
        assert.comment('- finish');
        assert.end();
    }
});

function checkConnectingLog(record, assert) {
    assert.ok([
        'connecting peers',
        'implementing affinity change'
    ].indexOf(record.msg) >= 0, 'expected connection logs due to peer advertise');
}

function destroyAll(destroyables, cb) {
    collectParallel(destroyables, destroyEach, cb);
}

function destroyEach(destroyable, i, done) {
    destroyable.destroy(done);
}

function createRemotes(cluster, N, opts, cb) {
    var done = CountedReadySignal(N);
    var res = [];
    for (var i = 0; i < N; ++i) {
        res[i] = cluster.createRemote(opts, done.signal);
        res[i].serverChannel.register('who', who);
    }
    done(cb);
    return res;
}

function who(req, res) {
    res.headers.as = 'raw';
    res.sendOk('', req.channel.hostPort);
}

function checkExitsTo(cluster, serviceName, cohort, desc, assert) {
    var cassert = CollapsedAssert();
    for (var i = 0; i < cohort.length; ++i) {
        var peer = cohort[i];
        cluster.checkExitPeers(cassert, {
            serviceName: serviceName,
            hostPort: peer.hostPort
        });
    }
    cassert.report(assert, 'expected exit peers for ' + desc);
}

/* eslint max-params: [2,8] */
function checkRequestsTo(serviceName, cohort, desc, chan, assert, cb) {
    var cassert = CollapsedAssert();
    sendMany(chan, 2 * cohort.length, {
        serviceName: serviceName,
        timeout: REQUEST_TIMEOUT
    }, 'who', '', '', function sent(err, res, arg2, arg3) {
        cassert.ifError(err, 'no unexpected error');
        var serverHostPort = String(arg3);
        cassert.ok(cohort.some(function isit(remote) {
            return remote.hostPort === serverHostPort;
        }), 'expected request to be served by one of ' + desc);
    }, function finish() {
        cassert.report(assert, 'expected requests to be served by ' + desc);
        cb();
    });
}

function sendMany(chan, N, opts, arg1, arg2, arg3, check, cb) {
    var sendsDone = CountedReadySignal(N);
    sendsDone(cb);
    for (var i = 0; i < sendsDone.counter; ++i) {
        chan.request(opts).send(arg1, arg2, arg3, done);
    }
    function done(err, res, resArg2, resArg3) {
        check(err, res, resArg2, resArg3);
        sendsDone.signal();
    }
}

function takeLogs(dbgLogtron) {
    var records = dbgLogtron._backend.records;
    dbgLogtron._backend.records = [];
    return records;
}

function checkAllLogs(cluster, assert, check) {
    var failed = false;
    assert.on('result', onResult);

    var records = takeLogs(cluster.logger);
    for (var i = 0; i < records.length; ++i) {
        var record = records[i];
        failed = false;
        check(record, assert);
        if (failed) {
            assert.comment(util.format(
                'UNEXPECTED LOG: %s %s: %j',
                record.levelName, record.msg, record._logData.fields
            ));
        }
    }
    assert.removeListener('result', onResult);

    function onResult(result) {
        if (!failed && typeof result === 'object') {
            failed = !result.ok;
        }
    }
}

function checkNoLogs(desc, cluster, assert) {
    var records = takeLogs(cluster.logger);
    assert.equal(records.length, 0, 'expected no logs from ' + desc);
    if (records.length) {
        for (var i = 0; i < records.length; ++i) {
            var record = records[i];
            assert.comment(util.format('UNEXPECTED LOG: %s %s: %j',
                record.levelName, record.msg, record._logData.fields
            ));
        }
    }
}

// creates dirst-stupid fixture tcp servers in place of each destroyed remote
function createTurnips(remotes, cb) {
    var turnips = [];
    turnips.takeConnLogs = takeConnLogs;
    collectParallel(remotes, createEachTurnip, cb);
    return turnips;

    function takeConnLogs() {
        var logs = [];
        for (var i = 0; i < turnips.length; ++i) {
            logs[i] = turnips[i].takeConnLog();
        }
        return logs;
    }

    function createEachTurnip(remote, i, done) {
        turnips[i] = createTurnip(remote, done);
    }
}

function createTurnip(remote, cb) {
    var turnip = {
        remote: remote,
        server: net.createServer(onConnection),
        connLog: [],
        destroy: destroy,
        takeConnLog: takeConnLog
    };

    var parts = remote.channel.hostPort.split(':');
    var host = parts[0];
    var port = parseInt(parts[1], 10);
    turnip.server.listen(port, host, cb);

    return turnip;

    function takeConnLog() {
        var log = turnip.connLog;
        turnip.connLog = [];
        return log;
    }

    function destroy(destroyCb) {
        turnip.server.close(destroyCb);
    }

    function onConnection(socket) {
        turnip.connLog.push({
            remoteAddress: socket.remoteAddress,
            remotePort: socket.remotePort
        });
        socket.end();
    }
}
<commit_msg>Less requests, smeared<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var collectParallel = require('collect-parallel/array');
var CountedReadySignal = require('ready-signal/counted');
var net = require('net');
var timers = require('timers');
var util = require('util');

var allocCluster = require('./lib/test-cluster.js');
var CollapsedAssert = require('./lib/collapsed-assert.js');

/* eslint-disable no-multi-spaces */
var PERIOD           = 100;
var REQUEST_TIMEOUT  = 200;
var REQUEST_FACTOR   = 1;
var COOL_OFF_PERIODS = 10;
var CLUSTER_SIZE     = 10;
var CHURN_FACTOR     = 0.5;
var K_VALUE          = 5;
var SERVICE_SIZE     = 10;

function fuzzedPeriods(N) {
    return 1.05 * N * PERIOD;
}

allocCluster.test('peer churn', {
    size: CLUSTER_SIZE,
    noBob: true,
    noSteve: true,
    noTCollector: true,

    remoteConfig: {
        'kValue.services': {
            'lucy': K_VALUE
        },
        'peerConnecter.period': PERIOD,
        'peerPruner.period': PERIOD,
        'peerReaper.period': PERIOD
    },

    whitelist: [

        // XXX do we care about the rare:
        // AUTOBAHN INFO: ignoring outresponse.send on a closed connection ~ { responseId: 5,

        ['info', 'reaping dead peers'],
        ['info', 'reaping dead peer'],
        ['info', 'draining peer'],

        ['error', 'Failed to register to hyperbahn for remote'],

        ['info', 'Peer drained and closed due to unadvertisement'],
        ['info', 'canceling peer drain'],
        ['info', 'draining pruned peer'],
        ['info', 'pruning peers'],
        ['info', 'stopping peer drain'],

        // XXX flappy logs but no harm
        ['warn', 'error while forwarding'],
        ['info', 'resetting connection']

    ]

}, function t(cluster, assert) {
    var serverOpts = {
        serviceName: 'lucy',
        registerEvery: 50
    };

    var clientOpts = {
        serviceName: 'kathy',
        registerEvery: 50
    };

    var client = cluster.createRemote(clientOpts, clientReady);
    var first = null;
    var second = null;
    var turnips = null;

    function clientReady() {
        assert.comment('- clientReady');
        checkAllLogs(cluster, assert, checkConnectingLog);
        first = createRemotes(cluster, SERVICE_SIZE, serverOpts, gotFirstRound);
    }

    function gotFirstRound() {
        assert.comment('- gotFirstRound');
        checkAllLogs(cluster, assert, checkConnectingLog);
        checkExitsTo(cluster, 'lucy', first, 'the first peers', assert);
        checkRequestsTo('lucy', first, 'the first peers', client.clientChannel, assert, setupSecondRound);
    }

    function setupSecondRound() {
        assert.comment('- setupSecondRound');
        checkNoLogs('first phase sends', cluster, assert);
        second = createRemotes(cluster, Math.round(CHURN_FACTOR * first.length), serverOpts, gotSecondRound);
    }

    function gotSecondRound() {
        assert.comment('- gotSecondRound');
        checkAllLogs(cluster, assert, checkConnectingLog);
        checkExitsTo(cluster, 'lucy', second, 'the second peers', assert);
        checkRequestsTo('lucy', first.concat(second), 'either generation', client.clientChannel, assert, thenShutdownFirstRound);
    }

    function thenShutdownFirstRound() {
        assert.comment('- thenShutdownFirstRound');
        collectParallel(first, function each(remote, i, done) {
            // half of them do an unad then destroy, the other half just go away
            if (i % 2 === 0) {
                remote.doUnregister(function unreged(err) {
                    assert.ifError(err, 'expected no unregister error');
                    remote.destroy(done);
                });
            } else {
                remote.destroy(done);
            }
        }, thenWaitForReaper);
    }

    function thenWaitForReaper() {
        assert.comment('- thenWaitForReaper');

        checkAllLogs(cluster, assert, function checkEachLog(record) {
            assert.ok([
                // happens for destructive shutdown
                'Failed to register to hyperbahn for remote',
                'expected registration failure during destruction',
                'resetting connection',

                // these happen due to the unads
                'Peer drained and closed due to unadvertisement',
                'canceling peer drain',
                'draining peer',
                'implementing affinity change',
                'stopping peer drain',
                'pruning peers'
            ].indexOf(record.msg) >= 0, 'expected reaping logs');
        });

        timers.setTimeout(thenTurnip, fuzzedPeriods(COOL_OFF_PERIODS));
    }

    function thenTurnip() {
        assert.comment('- thenTurnip');

        // checkNoLogs('reap', cluster, assert);

        checkAllLogs(cluster, assert, function checkEachLog(record) {
            assert.ok([
                // these happen more for the peers that didn't unad above
                'connecting peers',
                'resetting connection',
                'reaping dead peers',
                'reaping dead peer',
                'draining peer',
                'pruning peers'
            ].indexOf(record.msg) >= 0, 'expected peer churn logs');
        });

        turnips = createTurnips(first, thenWaitAndSee);
    }

    function thenWaitAndSee() {
        assert.comment('- thenWaitAndSee');

        checkNoLogs('turnip', cluster, assert);

        timers.setTimeout(function tendTurnips() {
            checkNoLogs('turnip tending', cluster, assert);
            checkTurnips();
            sendAfterChurn();
        }, fuzzedPeriods(COOL_OFF_PERIODS));
    }

    function checkTurnips() {
        var logs = turnips.takeConnLogs();
        for (var i = 0; i < logs.length; ++i) {
            var log = logs[i];
            assert.equal(log.length, 0, util.format(
                'expected no connections to turnip[%s] (%s)',
                i, turnips[i].remote.channel.hostPort
            ));
        }
    }

    function sendAfterChurn() {
        assert.comment('- sendAfterChurn');
        checkRequestsTo('lucy', second, 'the second peers', client.clientChannel, assert, thenDestroySecondRound);
    }

    function thenDestroySecondRound() {
        assert.comment('- thenDestroySecondRound');

        // XXX: these don't always happen, but when they do the client retries around them
        checkAllLogs(cluster, assert, function checkEachLog(record) {
            if (record.msg === 'error while forwarding') {
                var fields = record._logData.fields;
                assert.equal(fields.error.type, 'tchannel.connection.reset',
                    'expected connection reset while forwarding error');
            } else {
                assert.equal(record.msg, 'resetting connection',
                    'expected resetting connection log');
            }
        });

        checkTurnips();

        destroyAll(second.concat(turnips).concat([client]), finish);
    }

    function finish() {
        assert.comment('- finish');
        assert.end();
    }
});

function checkConnectingLog(record, assert) {
    assert.ok([
        'connecting peers',
        'implementing affinity change'
    ].indexOf(record.msg) >= 0, 'expected connection logs due to peer advertise');
}

function destroyAll(destroyables, cb) {
    collectParallel(destroyables, destroyEach, cb);
}

function destroyEach(destroyable, i, done) {
    destroyable.destroy(done);
}

function createRemotes(cluster, N, opts, cb) {
    var done = CountedReadySignal(N);
    var res = [];
    for (var i = 0; i < N; ++i) {
        res[i] = cluster.createRemote(opts, done.signal);
        res[i].serverChannel.register('who', who);
    }
    done(cb);
    return res;
}

function who(req, res) {
    res.headers.as = 'raw';
    res.sendOk('', req.channel.hostPort);
}

function checkExitsTo(cluster, serviceName, cohort, desc, assert) {
    var cassert = CollapsedAssert();
    for (var i = 0; i < cohort.length; ++i) {
        var peer = cohort[i];
        cluster.checkExitPeers(cassert, {
            serviceName: serviceName,
            hostPort: peer.hostPort
        });
    }
    cassert.report(assert, 'expected exit peers for ' + desc);
}

/* eslint max-params: [2,8] */
function checkRequestsTo(serviceName, cohort, desc, chan, assert, cb) {
    var cassert = CollapsedAssert();
    sendMany(chan, REQUEST_FACTOR * cohort.length, {
        serviceName: serviceName,
        timeout: REQUEST_TIMEOUT
    }, 'who', '', '', function sent(err, res, arg2, arg3) {
        cassert.ifError(err, 'no unexpected error');
        var serverHostPort = String(arg3);
        cassert.ok(cohort.some(function isit(remote) {
            return remote.hostPort === serverHostPort;
        }), 'expected request to be served by one of ' + desc);
    }, function finish() {
        cassert.report(assert, 'expected requests to be served by ' + desc);
        cb();
    });
}

function sendMany(chan, N, opts, arg1, arg2, arg3, check, cb) {
    var sendsDone = CountedReadySignal(N);
    sendsDone(cb);
    for (var i = 0; i < sendsDone.counter; ++i) {
        timers.setImmediate(doRequest);
    }

    function doRequest() {
        chan.request(opts).send(arg1, arg2, arg3, done);
    }

    function done(err, res, resArg2, resArg3) {
        check(err, res, resArg2, resArg3);
        sendsDone.signal();
    }
}

function takeLogs(dbgLogtron) {
    var records = dbgLogtron._backend.records;
    dbgLogtron._backend.records = [];
    return records;
}

function checkAllLogs(cluster, assert, check) {
    var failed = false;
    assert.on('result', onResult);

    var records = takeLogs(cluster.logger);
    for (var i = 0; i < records.length; ++i) {
        var record = records[i];
        failed = false;
        check(record, assert);
        if (failed) {
            assert.comment(util.format(
                'UNEXPECTED LOG: %s %s: %j',
                record.levelName, record.msg, record._logData.fields
            ));
        }
    }
    assert.removeListener('result', onResult);

    function onResult(result) {
        if (!failed && typeof result === 'object') {
            failed = !result.ok;
        }
    }
}

function checkNoLogs(desc, cluster, assert) {
    var records = takeLogs(cluster.logger);
    assert.equal(records.length, 0, 'expected no logs from ' + desc);
    if (records.length) {
        for (var i = 0; i < records.length; ++i) {
            var record = records[i];
            assert.comment(util.format('UNEXPECTED LOG: %s %s: %j',
                record.levelName, record.msg, record._logData.fields
            ));
        }
    }
}

// creates dirst-stupid fixture tcp servers in place of each destroyed remote
function createTurnips(remotes, cb) {
    var turnips = [];
    turnips.takeConnLogs = takeConnLogs;
    collectParallel(remotes, createEachTurnip, cb);
    return turnips;

    function takeConnLogs() {
        var logs = [];
        for (var i = 0; i < turnips.length; ++i) {
            logs[i] = turnips[i].takeConnLog();
        }
        return logs;
    }

    function createEachTurnip(remote, i, done) {
        turnips[i] = createTurnip(remote, done);
    }
}

function createTurnip(remote, cb) {
    var turnip = {
        remote: remote,
        server: net.createServer(onConnection),
        connLog: [],
        destroy: destroy,
        takeConnLog: takeConnLog
    };

    var parts = remote.channel.hostPort.split(':');
    var host = parts[0];
    var port = parseInt(parts[1], 10);
    turnip.server.listen(port, host, cb);

    return turnip;

    function takeConnLog() {
        var log = turnip.connLog;
        turnip.connLog = [];
        return log;
    }

    function destroy(destroyCb) {
        turnip.server.close(destroyCb);
    }

    function onConnection(socket) {
        turnip.connLog.push({
            remoteAddress: socket.remoteAddress,
            remotePort: socket.remotePort
        });
        socket.end();
    }
}
<|endoftext|>


<commit_before>/*
Google Summer of Code 2012: Automagic Music Maker

Primarily written by Myles Borins
Strongly influenced by GSOC Mentor Colin Clark
Using the Infusion framework and Flocking Library

The Automagic Music Maker is distributed under the terms the MIT or GPL2 Licenses.
Choose the license that best suits your project. The text of the MIT and GPL
licenses are at the root of the Piano directory.

*/

/*global jQuery, fluid, d3*/

var automm = automm || {};

(function () {
    "use strict";

    fluid.defaults("automm.piano", {
        gradeNames: ["fluid.viewComponent", "autoInit"],
        preInitFunction: "automm.piano.preInitFunction",
        postInitFunction: "automm.piano.postInitFunction",

        model: {
            firstNote: 60, // Middle C
            octaves: 1,
            octaveNotes: 12,
            padding: 50,
            pattern: ['white', 'black', 'white', 'black', 'white', 'white', 'black', 'white', 'black', 'white', 'black', 'white'],
            keys: {
                white: {
                    width: 50,
                    height: 200,
                    stroke: "black",
                    fill: "white",
                    highlight: "yellow",
                    selected: "blue",
                    notes: []
                },
                black: {
                    width: 30,
                    height: 125,
                    stroke: "black",
                    fill: "black",
                    highlight: "yellow",
                    selected: "blue",
                    notes: []
                }
            },
            viewBox: {
                width: null,
                height: null
            }
        },

        events: {
            afterUpdate: null,
            afterInstrumentUpdate: null,
            afterNoteCalc: null,
            getNoteCalc: null
        }
    });

    automm.piano.preInitFunction = function (that) {
        that.setup = function () {
            var i;
            that.model.keys.white.notes = [];
            that.model.keys.black.notes = [];

            for (i = that.model.firstNote; i < (that.model.firstNote + (that.model.octaves * that.model.octaveNotes)); i += 1) {
                that.model.keys[that.model.pattern[i % that.model.octaveNotes]].notes.push(i);
            }

            that.model.whiteNotes = that.model.keys.white.notes.length;
            that.model.blackNotes = that.model.keys.black.notes.length;

            that.updateValue("viewbox", {
                width: (that.model.keys.white.width * that.model.whiteNotes) + that.model.padding,
                height: that.model.keys.white.height + that.model.padding
            });

            // Calculate to create string neccesary to generate viewbox (should be in JSON?)
            that.model.viewbox.dim = "0 0 " + that.model.viewbox.width + " " + that.model.viewbox.height;
        };

        // Automation of drawing all the keys on the canvas
        that.drawNote = function (noteType, x, y, id) {
            var r = that.noteGroup.append("rect");
            r.style("stroke", noteType.stroke);
            r.style("fill", noteType.fill);
            r.attr("x", x);
            r.attr("y", y);
            r.attr("width", noteType.width);
            r.attr("height", noteType.height);
            r.attr("id", id);
            r.attr("class", "note");
            r.attr("noteType", noteType.fill);
        };

        // Automation of drawing all the keys on the canvas
        that.render = function () {
            var blackX = -(that.model.keys.black.width / 2),
                prevNote,
                blackCount = 0,
                i;

            if (that.model.keys.white.notes[0] > that.model.keys.black.notes[0]) {
                blackX = blackX - that.model.keys.white.width + (that.model.keys.black.width / 2);
            }
            // Draw White Keys
            for (i = 0; i < that.model.keys.white.notes.length; i += 1) {
                if (that.model.keys.white.notes[0] > that.model.keys.black.notes[0]) {
                    that.drawNote(that.model.keys.white, (i * that.model.keys.white.width) + that.model.keys.black.width / 2, 0, that.model.keys.white.notes[i]);
                } else {
                    that.drawNote(that.model.keys.white, i * that.model.keys.white.width, 0, that.model.keys.white.notes[i]);
                }
            }

            // Draw Black Keys
            for (i = that.model.firstNote; i < (that.model.octaves * that.model.octaveNotes) + that.model.firstNote; i += 1) {
                //get width going

                // If the current key in the pattern is black then draw it!
                if (that.model.pattern[i % that.model.octaveNotes] === "black") {
                    blackX = blackX + that.model.keys.white.width;
                    that.drawNote(that.model.keys.black, blackX, 0, that.model.keys.black.notes[blackCount]);
                    blackCount = blackCount + 1;
                }

                // If it is white, but the previous key was white, skip the key
                if (that.model.pattern[i % that.model.octaveNotes] === prevNote) {
                    blackX = blackX + that.model.keys.white.width;
                }

                // Keep track of previous key
                prevNote = that.model.pattern[i % that.model.octaveNotes];
            }
        };

        that.draw = function () {
            // Calculate it all
            that.setup();
            // Draw viewbox and subsequent group to draw keys into
            that.d3container = d3.select("#" + that.container.attr('id')).select('#piano');  // ??????
            var svg = that.d3container.append("svg");
            svg.attr("style", "height: 100%;");
            svg.attr("viewBox", that.model.viewbox.dim);
            svg.attr("role", "application");
            svg.attr("focusable", true);
            svg.attr("tabindex", "0");
            svg.attr("id", "viewBox");
            svg.attr("aria-labelledby", "ariaTitle");

            that.noteGroup = svg.append("g");
            that.noteGroup.attr("transform", "translate(" + that.model.padding / 2 + "," + that.model.padding / 2 + ")");
            that.noteGroup.attr("id", "noteGroup");
            that.noteGroup.attr("focusable", true);
            // Draw the keys
            that.render();
        };

        that.updateValue = function (param, value) {
            that.applier.requestChange(param, value);
        };

        that.update = function (param, value) {
            that.applier.requestChange(param, value);
            that.container.children("#piano").empty();
            that.draw();
            // Fire event that piano is drawn
            that.events.afterUpdate.fire();
        };

        that.sendNoteCalc = function () {
            that.events.afterNoteCalc.fire(that.model.keys);
        };
    };

    automm.piano.postInitFunction = function (that) {
        var pianoElements = that.container.find("#piano").length;
        if (that.model.auto && pianoElements < 1) {
            that.container.append("

");
            pianoElements = 1;
        }
        if (pianoElements > 0) {
            // Draw the svg
            that.draw();
            that.events.afterUpdate.fire();
            // Fire event that piano is drawn
            that.events.afterInstrumentUpdate.addListener(that.update);
            that.events.getNoteCalc.addListener(that.sendNoteCalc);
        }

    };
}());
<commit_msg>Added aria-describedby attribute to svg<commit_after>/*
Google Summer of Code 2012: Automagic Music Maker

Primarily written by Myles Borins
Strongly influenced by GSOC Mentor Colin Clark
Using the Infusion framework and Flocking Library

The Automagic Music Maker is distributed under the terms the MIT or GPL2 Licenses.
Choose the license that best suits your project. The text of the MIT and GPL
licenses are at the root of the Piano directory.

*/

/*global jQuery, fluid, d3*/

var automm = automm || {};

(function () {
    "use strict";

    fluid.defaults("automm.piano", {
        gradeNames: ["fluid.viewComponent", "autoInit"],
        preInitFunction: "automm.piano.preInitFunction",
        postInitFunction: "automm.piano.postInitFunction",

        model: {
            firstNote: 60, // Middle C
            octaves: 1,
            octaveNotes: 12,
            padding: 50,
            pattern: ['white', 'black', 'white', 'black', 'white', 'white', 'black', 'white', 'black', 'white', 'black', 'white'],
            keys: {
                white: {
                    width: 50,
                    height: 200,
                    stroke: "black",
                    fill: "white",
                    highlight: "yellow",
                    selected: "blue",
                    notes: []
                },
                black: {
                    width: 30,
                    height: 125,
                    stroke: "black",
                    fill: "black",
                    highlight: "yellow",
                    selected: "blue",
                    notes: []
                }
            },
            viewBox: {
                width: null,
                height: null
            }
        },

        events: {
            afterUpdate: null,
            afterInstrumentUpdate: null,
            afterNoteCalc: null,
            getNoteCalc: null
        }
    });

    automm.piano.preInitFunction = function (that) {
        that.setup = function () {
            var i;
            that.model.keys.white.notes = [];
            that.model.keys.black.notes = [];

            for (i = that.model.firstNote; i < (that.model.firstNote + (that.model.octaves * that.model.octaveNotes)); i += 1) {
                that.model.keys[that.model.pattern[i % that.model.octaveNotes]].notes.push(i);
            }

            that.model.whiteNotes = that.model.keys.white.notes.length;
            that.model.blackNotes = that.model.keys.black.notes.length;

            that.updateValue("viewbox", {
                width: (that.model.keys.white.width * that.model.whiteNotes) + that.model.padding,
                height: that.model.keys.white.height + that.model.padding
            });

            // Calculate to create string neccesary to generate viewbox (should be in JSON?)
            that.model.viewbox.dim = "0 0 " + that.model.viewbox.width + " " + that.model.viewbox.height;
        };

        // Automation of drawing all the keys on the canvas
        that.drawNote = function (noteType, x, y, id) {
            var r = that.noteGroup.append("rect");
            r.style("stroke", noteType.stroke);
            r.style("fill", noteType.fill);
            r.attr("x", x);
            r.attr("y", y);
            r.attr("width", noteType.width);
            r.attr("height", noteType.height);
            r.attr("id", id);
            r.attr("class", "note");
            r.attr("noteType", noteType.fill);
        };

        // Automation of drawing all the keys on the canvas
        that.render = function () {
            var blackX = -(that.model.keys.black.width / 2),
                prevNote,
                blackCount = 0,
                i;

            if (that.model.keys.white.notes[0] > that.model.keys.black.notes[0]) {
                blackX = blackX - that.model.keys.white.width + (that.model.keys.black.width / 2);
            }
            // Draw White Keys
            for (i = 0; i < that.model.keys.white.notes.length; i += 1) {
                if (that.model.keys.white.notes[0] > that.model.keys.black.notes[0]) {
                    that.drawNote(that.model.keys.white, (i * that.model.keys.white.width) + that.model.keys.black.width / 2, 0, that.model.keys.white.notes[i]);
                } else {
                    that.drawNote(that.model.keys.white, i * that.model.keys.white.width, 0, that.model.keys.white.notes[i]);
                }
            }

            // Draw Black Keys
            for (i = that.model.firstNote; i < (that.model.octaves * that.model.octaveNotes) + that.model.firstNote; i += 1) {
                //get width going

                // If the current key in the pattern is black then draw it!
                if (that.model.pattern[i % that.model.octaveNotes] === "black") {
                    blackX = blackX + that.model.keys.white.width;
                    that.drawNote(that.model.keys.black, blackX, 0, that.model.keys.black.notes[blackCount]);
                    blackCount = blackCount + 1;
                }

                // If it is white, but the previous key was white, skip the key
                if (that.model.pattern[i % that.model.octaveNotes] === prevNote) {
                    blackX = blackX + that.model.keys.white.width;
                }

                // Keep track of previous key
                prevNote = that.model.pattern[i % that.model.octaveNotes];
            }
        };

        that.draw = function () {
            // Calculate it all
            that.setup();
            // Draw viewbox and subsequent group to draw keys into
            that.d3container = d3.select("#" + that.container.attr('id')).select('#piano');  // ??????
            var svg = that.d3container.append("svg");
            svg.attr("style", "height: 100%;");
            svg.attr("viewBox", that.model.viewbox.dim);
            svg.attr("role", "application");
            svg.attr("focusable", true);
            svg.attr("tabindex", "0");
            svg.attr("id", "viewBox");
            svg.attr("aria-labelledby", "ariaTitle");
            svg.attr("aria-describedby", "ariaDescription");

            that.noteGroup = svg.append("g");
            that.noteGroup.attr("transform", "translate(" + that.model.padding / 2 + "," + that.model.padding / 2 + ")");
            that.noteGroup.attr("id", "noteGroup");
            that.noteGroup.attr("focusable", true);
            // Draw the keys
            that.render();
        };

        that.updateValue = function (param, value) {
            that.applier.requestChange(param, value);
        };

        that.update = function (param, value) {
            that.applier.requestChange(param, value);
            that.container.children("#piano").empty();
            that.draw();
            // Fire event that piano is drawn
            that.events.afterUpdate.fire();
        };

        that.sendNoteCalc = function () {
            that.events.afterNoteCalc.fire(that.model.keys);
        };
    };

    automm.piano.postInitFunction = function (that) {
        var pianoElements = that.container.find("#piano").length;
        if (that.model.auto && pianoElements < 1) {
            that.container.append("

");
            pianoElements = 1;
        }
        if (pianoElements > 0) {
            // Draw the svg
            that.draw();
            that.events.afterUpdate.fire();
            // Fire event that piano is drawn
            that.events.afterInstrumentUpdate.addListener(that.update);
            that.events.getNoteCalc.addListener(that.sendNoteCalc);
        }

    };
}());
<|endoftext|>


<commit_before>
var showGotoPage;

$(function() {
  $("#btnPlay").click(onPlay);
  $("#btnPause").click(onPause);
  $("#btnStop").click(onStop);
  $("#btnSettings").click(onSettings);
  $("#btnForward").click(onForward);
  $("#btnRewind").click(onRewind);
  $("#decrease-font-size").click(changeFontSize.bind(null, -1));
  $("#increase-font-size").click(changeFontSize.bind(null, +1));
  $("#decrease-window-size").click(changeWindowSize.bind(null, -1));
  $("#increase-window-size").click(changeWindowSize.bind(null, +1));

  updateButtons()
    .then(bgPageInvoke.bind(null, "getPlaybackState"))
    .then(function(state) {
      if (state != "PLAYING") $("#btnPlay").click();
    });
  setInterval(updateButtons, 500);

  refreshSize();
  checkAnnouncements();
});

function handleError(err) {
  if (!err) return;
  if (/^{/.test(err.message)) {
    var errInfo = JSON.parse(err.message);

    $("#status").html(formatError(errInfo)).show();
    $("#status a").click(function() {
      switch ($(this).attr("href")) {
        case "#open-extension-settings":
          brapi.tabs.create({url: "chrome://extensions/?id=" + brapi.runtime.id});
          break;
        case "#request-permissions":
          requestPermissions(errInfo.perms)
            .then(function(granted) {
              if (granted) $("#btnPlay").click();
            })
          break;
        case "#sign-in":
          getAuthToken({interactive: true})
            .then(function(token) {
              if (token) $("#btnPlay").click();
            })
            .catch(function(err) {
              $("#status").text(err.message).show();
            })
          break;
        case "#auth-wavenet":
          requestPermissions(config.wavenetPerms)
            .then(function(granted) {
              if (granted) bgPageInvoke("authWavenet");
            })
          break;
        case "#user-gesture":
          getBackgroundPage()
            .then(callMethod("userGestureActivate"))
            .then(function() {
              $("#btnPlay").click();
            })
          break;
      }
    })

    if (errInfo.code == "error_upload_pdf") {
      setTabUrl(undefined, "https://assets.lsdsoftware.com/read-aloud/page-scripts/pdf-upload.html");
    }
  }
  else {
    $("#status").text(err.message).show();
  }
}

function updateButtons() {
    return Promise.all([
      getSettings(),
      bgPageInvoke("getPlaybackState"),
      bgPageInvoke("getSpeechPosition")
    ])
  .then(spread(function(settings, state, speechPos) {
    $("#imgLoading").toggle(state == "LOADING");
    $("#btnSettings").toggle(state == "STOPPED");
    $("#btnPlay").toggle(state == "PAUSED" || state == "STOPPED");
    $("#btnPause").toggle(state == "PLAYING");
    $("#btnStop").toggle(state == "PAUSED" || state == "PLAYING" || state == "LOADING");
    $("#btnForward, #btnRewind").toggle(state == "PLAYING");
    $("#highlight, #toolbar").toggle(Boolean(settings.showHighlighting != null ? settings.showHighlighting : defaults.showHighlighting) && (state == "LOADING" || state == "PAUSED" || state == "PLAYING"));

    if ((settings.showHighlighting != null ? settings.showHighlighting : defaults.showHighlighting) && speechPos) {
      var pos = speechPos;
      var elem = $("#highlight");
      if (elem.data("texts") != pos.texts) {
        elem.data({texts: pos.texts, index: -1});
        elem.empty();
        for (var i=0; i<pos.texts.length; i++) {
          var html = escapeHtml(pos.texts[i]).replace(/\r?\n/g, "
");
          $("").html(html).appendTo(elem).css("cursor", "pointer").click(onSeek.bind(null, i));
        }
      }
      if (elem.data("index") != pos.index) {
        elem.data("index", pos.index);
        elem.children(".active").removeClass("active");
        var child = elem.children().eq(pos.index).addClass("active");
        if (child.length) {
        var childTop = child.position().top;
        var childBottom = childTop + child.outerHeight();
        if (childTop < 0 || childBottom >= elem.height()) elem.animate({scrollTop: elem[0].scrollTop + childTop - 10});
        }
      }
    }
  }));
}

function onPlay() {
  $("#status").hide();
  bgPageInvoke("play")
    .then(updateButtons)
    .catch(handleError)
}

function onPause() {
  bgPageInvoke("pause")
    .then(updateButtons)
    .catch(handleError)
}

function onStop() {
  bgPageInvoke("stop")
    .then(updateButtons)
    .catch(handleError)
}

function onSettings() {
  location.href = "options.html?referer=popup.html";
}

function onForward() {
  bgPageInvoke("forward")
    .then(updateButtons)
    .catch(handleError)
}

function onRewind() {
  bgPageInvoke("rewind")
    .then(updateButtons)
    .catch(handleError)
}

function onSeek(n) {
  bgPageInvoke("seek", [n])
    .catch(handleError)
}

function changeFontSize(delta) {
  getSettings(["highlightFontSize"])
    .then(function(settings) {
      var newSize = (settings.highlightFontSize || defaults.highlightFontSize) + delta;
      if (newSize >= 1 && newSize <= 8) return updateSettings({highlightFontSize: newSize}).then(refreshSize);
    })
    .catch(handleError)
}

function changeWindowSize(delta) {
  getSettings(["highlightWindowSize"])
    .then(function(settings) {
      var newSize = (settings.highlightWindowSize || defaults.highlightWindowSize) + delta;
      if (newSize >= 1 && newSize <= 3) return updateSettings({highlightWindowSize: newSize}).then(refreshSize);
    })
    .catch(handleError)
}

function refreshSize() {
  return getSettings(["highlightFontSize", "highlightWindowSize"])
    .then(function(settings) {
      var fontSize = getFontSize(settings);
      var windowSize = getWindowSize(settings);
      $("#highlight").css({
        "font-size": fontSize,
        width: isMobileOS() ? "100%" : windowSize[0],
        height: windowSize[1]
      })
    })
  function getFontSize(settings) {
    switch (settings.highlightFontSize || defaults.highlightFontSize) {
      case 1: return ".9em";
      case 2: return "1em";
      case 3: return "1.1em";
      case 4: return "1.2em";
      case 5: return "1.3em";
      case 6: return "1.4em";
      case 7: return "1.5em";
      default: return "1.6em";
    }
  }
  function getWindowSize(settings) {
    switch (settings.highlightWindowSize || defaults.highlightWindowSize) {
      case 1: return [430, 330];
      case 2: return [550, 420];
      default: return [750, 450];
    }
  }
}

function checkAnnouncements() {
  var now = new Date().getTime();
  getSettings(["announcement"])
    .then(function(settings) {
      var ann = settings.announcement;
      if (ann && ann.expire > now)
        return ann;
      else
        return ajaxGet(config.serviceUrl + "/read-aloud/announcement")
          .then(JSON.parse)
          .then(function(result) {
            result.expire = now + 6*3600*1000;
            if (ann && result.id == ann.id) {
              result.lastShown = ann.lastShown;
              result.disabled = ann.disabled;
            }
            updateSettings({announcement: result});
            return result;
          })
    })
    .then(function(ann) {
      if (ann.text && !ann.disabled) {
        if (!ann.lastShown || now-ann.lastShown > ann.period*60*1000) {
          showAnnouncement(ann);
          ann.lastShown = now;
          updateSettings({announcement: ann});
        }
      }
    })
}

function showAnnouncement(ann) {
  var html = escapeHtml(ann.text).replace(/\[(.*?)\]/g, "$1").replace(/\n/g, "
");
  $("#footer").html(html).addClass("announcement");
  if (ann.disableIfClick)
    $("#footer a").click(function() {
      ann.disabled = true;
      updateSettings({announcement: ann});
    })
}
<commit_msg>bug fix<commit_after>
var showGotoPage;

$(function() {
  $("#btnPlay").click(onPlay);
  $("#btnPause").click(onPause);
  $("#btnStop").click(onStop);
  $("#btnSettings").click(onSettings);
  $("#btnForward").click(onForward);
  $("#btnRewind").click(onRewind);
  $("#decrease-font-size").click(changeFontSize.bind(null, -1));
  $("#increase-font-size").click(changeFontSize.bind(null, +1));
  $("#decrease-window-size").click(changeWindowSize.bind(null, -1));
  $("#increase-window-size").click(changeWindowSize.bind(null, +1));

  updateButtons()
    .then(bgPageInvoke.bind(null, "getPlaybackState"))
    .then(function(state) {
      if (state != "PLAYING") $("#btnPlay").click();
    });
  setInterval(updateButtons, 500);

  refreshSize();
  checkAnnouncements();
});

function handleError(err) {
  if (!err) return;
  if (/^{/.test(err.message)) {
    var errInfo = JSON.parse(err.message);

    $("#status").html(formatError(errInfo)).show();
    $("#status a").click(function() {
      switch ($(this).attr("href")) {
        case "#open-extension-settings":
          brapi.tabs.create({url: "chrome://extensions/?id=" + brapi.runtime.id});
          break;
        case "#request-permissions":
          requestPermissions(errInfo.perms)
            .then(function(granted) {
              if (granted) $("#btnPlay").click();
            })
          break;
        case "#sign-in":
          getAuthToken({interactive: true})
            .then(function(token) {
              if (token) $("#btnPlay").click();
            })
            .catch(function(err) {
              $("#status").text(err.message).show();
            })
          break;
        case "#auth-wavenet":
          requestPermissions(config.wavenetPerms)
            .then(function(granted) {
              if (granted) bgPageInvoke("authWavenet");
            })
          break;
        case "#user-gesture":
          getBackgroundPage()
            .then(callMethod("userGestureActivate"))
            .then(function() {
              $("#btnPlay").click();
            })
          break;
      }
    })

    if (errInfo.code == "error_upload_pdf") {
      setTabUrl(undefined, "https://assets.lsdsoftware.com/read-aloud/page-scripts/pdf-upload.html");
    }
  }
  else {
    $("#status").text(err.message).show();
  }
}

function updateButtons() {
    return Promise.all([
      getSettings(),
      bgPageInvoke("getPlaybackState"),
      bgPageInvoke("getSpeechPosition")
    ])
  .then(spread(function(settings, state, speechPos) {
    $("#imgLoading").toggle(state == "LOADING");
    $("#btnSettings").toggle(state == "STOPPED");
    $("#btnPlay").toggle(state == "PAUSED" || state == "STOPPED");
    $("#btnPause").toggle(state == "PLAYING");
    $("#btnStop").toggle(state == "PAUSED" || state == "PLAYING" || state == "LOADING");
    $("#btnForward, #btnRewind").toggle(state == "PLAYING");
    $("#highlight, #toolbar").toggle(Boolean(settings.showHighlighting != null ? settings.showHighlighting : defaults.showHighlighting) && (state == "LOADING" || state == "PAUSED" || state == "PLAYING"));

    if ((settings.showHighlighting != null ? settings.showHighlighting : defaults.showHighlighting) && speechPos) {
      var pos = speechPos;
      var elem = $("#highlight");
      if (!elem.data("texts") || elem.data("texts").length != pos.texts.length || elem.data("texts").some(function(text,i) {return text != pos.texts[i]})) {
        elem.data({texts: pos.texts, index: -1});
        elem.empty();
        for (var i=0; i<pos.texts.length; i++) {
          var html = escapeHtml(pos.texts[i]).replace(/\r?\n/g, "
");
          $("").html(html).appendTo(elem).css("cursor", "pointer").click(onSeek.bind(null, i));
        }
      }
      if (elem.data("index") != pos.index) {
        elem.data("index", pos.index);
        elem.children(".active").removeClass("active");
        var child = elem.children().eq(pos.index).addClass("active");
        if (child.length) {
        var childTop = child.position().top;
        var childBottom = childTop + child.outerHeight();
        if (childTop < 0 || childBottom >= elem.height()) elem.animate({scrollTop: elem[0].scrollTop + childTop - 10});
        }
      }
    }
  }));
}

function onPlay() {
  $("#status").hide();
  bgPageInvoke("play")
    .then(updateButtons)
    .catch(handleError)
}

function onPause() {
  bgPageInvoke("pause")
    .then(updateButtons)
    .catch(handleError)
}

function onStop() {
  bgPageInvoke("stop")
    .then(updateButtons)
    .catch(handleError)
}

function onSettings() {
  location.href = "options.html?referer=popup.html";
}

function onForward() {
  bgPageInvoke("forward")
    .then(updateButtons)
    .catch(handleError)
}

function onRewind() {
  bgPageInvoke("rewind")
    .then(updateButtons)
    .catch(handleError)
}

function onSeek(n) {
  bgPageInvoke("seek", [n])
    .catch(handleError)
}

function changeFontSize(delta) {
  getSettings(["highlightFontSize"])
    .then(function(settings) {
      var newSize = (settings.highlightFontSize || defaults.highlightFontSize) + delta;
      if (newSize >= 1 && newSize <= 8) return updateSettings({highlightFontSize: newSize}).then(refreshSize);
    })
    .catch(handleError)
}

function changeWindowSize(delta) {
  getSettings(["highlightWindowSize"])
    .then(function(settings) {
      var newSize = (settings.highlightWindowSize || defaults.highlightWindowSize) + delta;
      if (newSize >= 1 && newSize <= 3) return updateSettings({highlightWindowSize: newSize}).then(refreshSize);
    })
    .catch(handleError)
}

function refreshSize() {
  return getSettings(["highlightFontSize", "highlightWindowSize"])
    .then(function(settings) {
      var fontSize = getFontSize(settings);
      var windowSize = getWindowSize(settings);
      $("#highlight").css({
        "font-size": fontSize,
        width: isMobileOS() ? "100%" : windowSize[0],
        height: windowSize[1]
      })
    })
  function getFontSize(settings) {
    switch (settings.highlightFontSize || defaults.highlightFontSize) {
      case 1: return ".9em";
      case 2: return "1em";
      case 3: return "1.1em";
      case 4: return "1.2em";
      case 5: return "1.3em";
      case 6: return "1.4em";
      case 7: return "1.5em";
      default: return "1.6em";
    }
  }
  function getWindowSize(settings) {
    switch (settings.highlightWindowSize || defaults.highlightWindowSize) {
      case 1: return [430, 330];
      case 2: return [550, 420];
      default: return [750, 450];
    }
  }
}

function checkAnnouncements() {
  var now = new Date().getTime();
  getSettings(["announcement"])
    .then(function(settings) {
      var ann = settings.announcement;
      if (ann && ann.expire > now)
        return ann;
      else
        return ajaxGet(config.serviceUrl + "/read-aloud/announcement")
          .then(JSON.parse)
          .then(function(result) {
            result.expire = now + 6*3600*1000;
            if (ann && result.id == ann.id) {
              result.lastShown = ann.lastShown;
              result.disabled = ann.disabled;
            }
            updateSettings({announcement: result});
            return result;
          })
    })
    .then(function(ann) {
      if (ann.text && !ann.disabled) {
        if (!ann.lastShown || now-ann.lastShown > ann.period*60*1000) {
          showAnnouncement(ann);
          ann.lastShown = now;
          updateSettings({announcement: ann});
        }
      }
    })
}

function showAnnouncement(ann) {
  var html = escapeHtml(ann.text).replace(/\[(.*?)\]/g, "$1").replace(/\n/g, "
");
  $("#footer").html(html).addClass("announcement");
  if (ann.disableIfClick)
    $("#footer a").click(function() {
      ann.disabled = true;
      updateSettings({announcement: ann});
    })
}
<|endoftext|>


<commit_before>// MIT License

// Copyright (c) 2017 Clement Quaresma

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

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

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

var todayDate = getTodayDate();
var office365 = "https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=e05d4149-1624-4627-a5ba-7472a39e43ab&redirect_uri=https%3A%2F%2Fintra.epitech.eu%2Fauth%2Foffice365&state=%2F";
var firstDate;
var lastDate;
var registeredEvents;

$(document).ready(function() {

    $("#weeklyDatePicker").datetimepicker({
        format: 'YYYY-MM-DD',
        locale: 'fr'
    });

    $('#weeklyDatePicker').on('dp.change', function(e) {
        var value = $("#weeklyDatePicker").val();
        firstDate = moment(value, "YYYY-MM-DD").day(1).format("YYYY-MM-DD");
        lastDate = moment(value, "YYYY-MM-DD").day(7).format("YYYY-MM-DD");
        $("#weeklyDatePicker").val(moment(firstDate).format("DD-MM-YYYY") + " - " +
				   moment(lastDate).format("DD-MM-YYYY"));
        $("#planning").empty();
        $("#info").empty();
        getTodayPlanning();
    });

    $('#planningFilter').click(function() {
      $(this).toggleClass('toggle');
      $(".input-group").toggleClass('toggle');
      if ($("#planningFilter").html() == "Cette semaine") {
        $("#planningFilter").html("Aujourd'hui");
        firstDate = null;
        lastDate = null;
      } else {
        $("#planningFilter").html("Cette semaine");
        firstDate = moment(todayDate, "YYYY-MM-DD").day(1).format("YYYY-MM-DD");
        lastDate = moment(todayDate, "YYYY-MM-DD").day(7).format("YYYY-MM-DD");
      }
      $("#planning").empty();
      $("#info").empty();
      getTodayPlanning();
    });

    $("#download").click(function(){
      if (registeredEvents.length > 0) {
        downloadPlanning();
      }
    });
});

function getTodayDate() {
    var today = new Date();
    var dd = today.getDate();
    var mm = today.getMonth() + 1; //January is 0!
    var yyyy = today.getFullYear();

    if (dd < 10) {
        dd = '0' + dd;
    }

    if (mm < 10) {
        mm = '0' + mm;
    }

    today = yyyy + '-' + mm + '-' + dd;
    return today;
}

function getRegisteredEvents(result) {
    var results = [];

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

        var obj = result[i];

        for (var key in obj) {
            var value = obj[key];

            if (key == "event_registered" && (value == "registered" || value == "present")) {
                results.push(obj);
            }
        }
    }
    results.sort(function(a, b) {
        return new Date(a.start) - new Date(b.start);
    });
    return results;
}

function getLink(data) {
    return ('' + data.acti_title + '');
}

function downloadPlanning() {
  'use strict';

  var ical = require(["js/ical-generator/"]),
      cal = ical({
        domain: 'intra.epitech.eu',
        prodId: '//clement.quaresma//Epitech-Planning//FR',
        name: 'Epitech-Planning'
      });

  $.each(registeredEvents, function(i, item) {
    var eventBegin = new Date(registeredEvents[i].start),
        eventEnding = new Date(registeredEvents[i].end),
        event = cal.createEvent({
          start: eventBegin,
          end: eventEnding,
          summary: registeredEvents[i].acti_title,
          description: registeredEvents[i].acti_title,
          url: getLink(registeredEvents[i])
        });
  });
  window.open("data:text/calendar;charset=utf8," + escape(cal.toString()));
}

function displayPlanning() {
    if (registeredEvents.length === 0) {
        $('
').text((!firstDate ? "Rien aujourd'hui !" : "Rien cette semaine !")).appendTo('#info');
    } else {
        $(function() {
            var currentDay;

            $.each(registeredEvents, function(i, item) {
                var start = new Date(registeredEvents[i].start);
                if (start.getDay() !== currentDay) {
                    currentDay = start.getDay();
                    var options = {weekday: "long", year: "numeric", month: "long", day: "numeric"};
                    $('
').append(
                        $('').text(start.toLocaleDateString("fr-FR", options))
                    ).appendTo('#planning');
                }

                var end = new Date(registeredEvents[i].end);

                $('
').append(
                    $('').append(getLink(registeredEvents[i])),
                    $('').text(start.getHours() + "h" + (start.getMinutes() < 10 ? '0' : '') + start.getMinutes()),
                    $('').text(end.getHours() + "h" + (end.getMinutes() < 10 ? '0' : '') + end.getMinutes()),
                    $('').text(registeredEvents[i].room.code.substring(registeredEvents[i].room.code.lastIndexOf("/") + 1))
                ).appendTo('#planning');

                if (registeredEvents[i].event_registered == "present") {
                  $(".event" + i).addClass("bg-success");
                }
            });

        });
    }
}

function getTodayPlanning() {
    var url;
    if (!firstDate && !lastDate) {
        url = 'https://intra.epitech.eu/planning/load?format=json&start=' +
            todayDate + '&end=' + todayDate;
    } else {
        url = 'https://intra.epitech.eu/planning/load?format=json&start=' +
            firstDate + '&end=' + lastDate;
    }

    console.log(url);
    var req = new XMLHttpRequest();
    req.open('GET', url, true);

    req.onload = function() {
        if (this.readyState === XMLHttpRequest.DONE) {
            if (this.status === 200) {
                var json = req.responseText;
                var result = JSON.parse(json);
                registeredEvents = getRegisteredEvents(result);

                /* Local storage for caching
                 ***************************
                 * localStorage.setItem('save', JSON.stringify(registeredEvents));
                 */
                console.log("Saving planning...");
                displayPlanning();
            }
            if (this.status === 401) {
                $('
').text("Connectez vous !").appendTo('#planning');
                $('#planning').prepend('');
            }
        }
    };
    req.send();
}

document.addEventListener('DOMContentLoaded', function() {
  /* Local storage for caching
   ***************************
   *  var save = localStorage.getItem('save');
   * console.log(save);
   * if (save !== null) {
   *    console.log("Retrieving saved planning...");
   *     save = JSON.parse(save);
   *    displayPlanning(save);
   * } else {
   *    console.log("New planning");
   *     getTodayPlanning();
   *}
   */
    getTodayPlanning();
});
<commit_msg>Fix date issue<commit_after>// MIT License

// Copyright (c) 2017 Clement Quaresma

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

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

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

var todayDate = getTodayDate();
var office365 = "https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=e05d4149-1624-4627-a5ba-7472a39e43ab&redirect_uri=https%3A%2F%2Fintra.epitech.eu%2Fauth%2Foffice365&state=%2F";
var firstDate;
var lastDate;
var registeredEvents;

$(document).ready(function() {

    $("#weeklyDatePicker").datetimepicker({
        format: 'DD-MM-YYYY',
        locale: 'fr'
    });

    $('#weeklyDatePicker').on('dp.change', function(e) {
        var value = $("#weeklyDatePicker").val();
        firstDate = moment(value, "DD-MM-YYYY").day(1).format("DD-MM-YYYY");
        lastDate = moment(value, "DD-MM-YYYY").day(7).format("DD-MM-YYYY");
        $("#weeklyDatePicker").val(firstDate + " - " + lastDate);
        $("#planning").empty();
        $("#info").empty();
        getTodayPlanning();
    });

    $('#planningFilter').click(function() {
	$(this).toggleClass('toggle');
	$(".input-group").toggleClass('toggle');
	if ($("#planningFilter").html() == "Cette semaine") {
            $("#planningFilter").html("Aujourd'hui");
            firstDate = null;
            lastDate = null;
	} else {
            $("#planningFilter").html("Cette semaine");
            firstDate = moment(todayDate, "DD-MM-YYYY").day(1).format("DD-MM-YYYY");
            lastDate = moment(todayDate, "DD-MM-YYYY").day(7).format("DD-MM-YYYY");
	}
	$("#planning").empty();
	$("#info").empty();
	getTodayPlanning();
    });

    $("#download").click(function(){
	if (registeredEvents.length > 0) {
            downloadPlanning();
	}
    });
});

function getTodayDate() {
    var today = new Date();
    var dd = today.getDate();
    var mm = today.getMonth() + 1; //January is 0!
    var yyyy = today.getFullYear();

    if (dd < 10) {
        dd = '0' + dd;
    }

    if (mm < 10) {
        mm = '0' + mm;
    }

    today = yyyy + '-' + mm + '-' + dd;
    return today;
}

function getRegisteredEvents(result) {
    var results = [];

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

        var obj = result[i];

        for (var key in obj) {
            var value = obj[key];

            if (key == "event_registered" && (value == "registered" || value == "present")) {
                results.push(obj);
            }
        }
    }
    results.sort(function(a, b) {
        return new Date(a.start) - new Date(b.start);
    });
    return results;
}

function getLink(data) {
    return ('' + data.acti_title + '');
}

function downloadPlanning() {
  'use strict';

  var ical = require(["js/ical-generator/"]),
      cal = ical({
        domain: 'intra.epitech.eu',
        prodId: '//clement.quaresma//Epitech-Planning//FR',
        name: 'Epitech-Planning'
      });

  $.each(registeredEvents, function(i, item) {
    var eventBegin = new Date(registeredEvents[i].start),
        eventEnding = new Date(registeredEvents[i].end),
        event = cal.createEvent({
          start: eventBegin,
          end: eventEnding,
          summary: registeredEvents[i].acti_title,
          description: registeredEvents[i].acti_title,
          url: getLink(registeredEvents[i])
        });
  });
  window.open("data:text/calendar;charset=utf8," + escape(cal.toString()));
}

function displayPlanning() {
    if (registeredEvents.length === 0) {
        $('
').text((!firstDate ? "Rien aujourd'hui !" : "Rien cette semaine !")).appendTo('#info');
    } else {
        $(function() {
            var currentDay;

            $.each(registeredEvents, function(i, item) {
                var start = new Date(registeredEvents[i].start);
                if (start.getDay() !== currentDay) {
                    currentDay = start.getDay();
                    var options = {weekday: "long", year: "numeric", month: "long", day: "numeric"};
                    $('
').append(
                        $('').text(start.toLocaleDateString("fr-FR", options))
                    ).appendTo('#planning');
                }

                var end = new Date(registeredEvents[i].end);

                $('
').append(
                    $('').append(getLink(registeredEvents[i])),
                    $('').text(start.getHours() + "h" + (start.getMinutes() < 10 ? '0' : '') + start.getMinutes()),
                    $('').text(end.getHours() + "h" + (end.getMinutes() < 10 ? '0' : '') + end.getMinutes()),
                    $('').text(registeredEvents[i].room.code.substring(registeredEvents[i].room.code.lastIndexOf("/") + 1))
                ).appendTo('#planning');

                if (registeredEvents[i].event_registered == "present") {
                  $(".event" + i).addClass("bg-success");
                }
            });

        });
    }
}

function getTodayPlanning() {
    var url;
    if (!firstDate && !lastDate) {
        url = 'https://intra.epitech.eu/planning/load?format=json&start=' +
            todayDate + '&end=' + todayDate;
    } else {
        url = 'https://intra.epitech.eu/planning/load?format=json&start=' +
            moment(firstDate, "DD-MM-YYYY").format("YYYY-MM-DD") + '&end=' + moment(lastDate, "DD-MM-YYYY").format("YYYY-MM-DD");
    }

    console.log(url);
    var req = new XMLHttpRequest();
    req.open('GET', url, true);

    req.onload = function() {
        if (this.readyState === XMLHttpRequest.DONE) {
            if (this.status === 200) {
                var json = req.responseText;
                var result = JSON.parse(json);
                registeredEvents = getRegisteredEvents(result);

                /* Local storage for caching
                 ***************************
                 * localStorage.setItem('save', JSON.stringify(registeredEvents));
                 */
                console.log("Saving planning...");
                displayPlanning();
            }
            if (this.status === 401) {
                $('
').text("Connectez vous !").appendTo('#planning');
                $('#planning').prepend('');
            }
        }
    };
    req.send();
}

document.addEventListener('DOMContentLoaded', function() {
  /* Local storage for caching
   ***************************
   *  var save = localStorage.getItem('save');
   * console.log(save);
   * if (save !== null) {
   *    console.log("Retrieving saved planning...");
   *     save = JSON.parse(save);
   *    displayPlanning(save);
   * } else {
   *    console.log("New planning");
   *     getTodayPlanning();
   *}
   */
    getTodayPlanning();
});
<|endoftext|>


<commit_before>/* Pop-up to ask for cookie permission */
function openPopup() {
  var popup = document.createElement("div");
  document.body.appendChild(popup);
  popup.setAttribute("id", "popup");
  popup.setAttribute("style", "position:fixed;left:0;right:0;top:75%;height:25%;width:99.78%;border:solid 0.1em #F7F7F7;background-color: #2E2E2E;margin:0 auto;text-align:center;");

  var xContainer = document.createElement("div");
  popup.appendChild(xContainer);
  xContainer.setAttribute("style", "float:right;");

  var xImage = document.createElement("img");
  xContainer.appendChild(xImage);
  xImage.setAttribute("onclick", "$('#popup').hide();");
  xImage.setAttribute("src", "img/home/x.png");
  xImage.setAttribute("width", "10");

  popup = $('div#popup');

  var title = $('
Hinweis:
');
  title.attr("class", "color_cloud");
  title.attr("style", "margin-bottom: 0px; padding-bottom: 0px");
  popup.append(title);

  const legalText = "Bei der weiteren Nutzung dieser Seite stimmen Sie zu, dass Daten über ihren Zugriff auf dem Server gespeichert werden. Weiterhin stimmen Sie die Nutzung von Cookies zu. Diese dienen dazu eine bessere Benutzererfahrung zu schaffen. Möchten Sie fortfahren?";
  var legalTextP = $("
" + legalText + "
");
  legalTextP.attr('class', 'color_cloud');
  legalTextP.attr('style', 'padding: 6px; font-size: 15px; padding-top: 0; margin-top: 0; margin-bottom: 0;');
  popup.append(legalTextP);

  var OKbutton = $('<button>Ok</button>')
  OKbutton.attr('style', 'margin-top: 0; background-color: #132d52; border-style: solid; border-width: 1px; border-radius: 5px;');
  OKbutton.attr('class', 'color_cloud');
  OKbutton.click(function() {
    popup.hide();
    setCookie('visit', 'visited', 90);
  });
  popup.append(OKbutton);
}

//Load Pop-up
if (document.cookie) {
 //show no pop-up
}
else {
  openPopup();
}
<commit_msg>changed logic<commit_after>/* Pop-up to ask for cookie permission */
function openPopup() {
  var popup = document.createElement("div");
  document.body.appendChild(popup);
  popup.setAttribute("id", "popup");
  popup.setAttribute("style", "position:fixed;left:0;right:0;top:75%;height:25%;width:99.78%;border:solid 0.1em #F7F7F7;background-color: #2E2E2E;margin:0 auto;text-align:center;");

  var xContainer = document.createElement("div");
  popup.appendChild(xContainer);
  xContainer.setAttribute("style", "float:right;");

  var xImage = document.createElement("img");
  xContainer.appendChild(xImage);
  xImage.setAttribute("onclick", "$('#popup').hide();");
  xImage.setAttribute("src", "img/home/x.png");
  xImage.setAttribute("width", "10");

  popup = $('div#popup');

  var title = $('
Hinweis:
');
  title.attr("class", "color_cloud");
  title.attr("style", "margin-bottom: 0px; padding-bottom: 0px");
  popup.append(title);

  const legalText = "Bei der weiteren Nutzung dieser Seite stimmen Sie zu, dass Daten über ihren Zugriff auf dem Server gespeichert werden. Weiterhin stimmen Sie die Nutzung von Cookies zu. Diese dienen dazu eine bessere Benutzererfahrung zu schaffen. Möchten Sie fortfahren?";
  var legalTextP = $("
" + legalText + "
");
  legalTextP.attr('class', 'color_cloud');
  legalTextP.attr('style', 'padding: 6px; font-size: 15px; padding-top: 0; margin-top: 0; margin-bottom: 0;');
  popup.append(legalTextP);

  var OKbutton = $('<button>Ok</button>')
  OKbutton.attr('style', 'margin-top: 0; background-color: #132d52; border-style: solid; border-width: 1px; border-radius: 5px;');
  OKbutton.attr('class', 'color_cloud');
  OKbutton.click(function() {
    popup.hide();
    setCookie('visit', 'visited', 90);
  });
  popup.append(OKbutton);
}

//Load Pop-up
if (document.cookie = "") {
 openPopup();
}
else {
  //show no pop-up
}
<|endoftext|>


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

$('#inbox_link').click(function() {
	$('#inbox').show();
	$('#send').hide();
});
$('#send_link').click(function() {
	$('#inbox').hide();
	$('#send').show();
});

registerOnLoadFunction(function() {
	if (storage.getUnencrypted("number_id") === undefined) {
		chrome.tabs.create({url: "options.html"});
	} else {
		function fillMessages() {
			var MAX_MESSAGES_PER_CONVERSATION = 4;
			var MAX_CONVERSATIONS = 5;

			var conversations = [];

			var messageMap = getMessageMap();
			for (conversation in messageMap) {
				var messages = messageMap[conversation];
				messages.sort(function(a, b) { return b.timestamp - a.timestamp; });
				conversations[conversations.length] = messages;
			}

			conversations.sort(function(a, b) { return b[0].timestamp - a[0].timestamp });

			var ul = $('#conversations');
			ul.html('');
			for (var i = 0; i < MAX_CONVERSATIONS && i < conversations.length; i++) {
				var conversation = conversations[i];
				var messages = $('
');
				for (var j = 0; j < MAX_MESSAGES_PER_CONVERSATION && j < conversation.length; j++) {
					var message = conversation[j];
					$('
').
						append($('
')).
						append($('
').
							append($('').text(message.message)).
							append($('').text("From: " + message.sender + ", at: " + timestampToHumanReadable(message.timestamp)))
						).appendTo(messages);
				}
				var button = $('<button id="button' + i + '">').text('Send');
				var input = $('<input id="text' + i + '">');
				$('
').
					append(messages).
					append($("<form class='container'>").append(input).append(button)).
					appendTo(ul);
				button.click(function() {
					var sendDestinations = [conversation[0].sender];
					if (conversation[0].group) {
							sendDestinations = conversation[0].group.members;
					}

					var messageProto = new PushMessageContentProtobuf();
					messageProto.body = input.val();

					sendMessageToNumbers(sendDestinations, messageProto, function(result) {
						console.log("Sent message: " + result);
					});
				});
			}
		}

		$(window).bind('storage', function(e) {
			console.log("Got localStorage update for key " + e.key);
			if (event.key == "emessageMap")//TODO: Fix when we get actual encryption
				fillMessages();
		});
		fillMessages();
		$('.my-number').text(storage.getUnencrypted("number_id").split(".")[0]);
		storage.putUnencrypted("unreadCount", 0);
		chrome.browserAction.setBadgeText({text: ""});
		$("#me").click(function() {
			$('#popup_send_numbers').val($('.my-number').text());
		});

		$("#popup_send_button").click(function() {
			var numbers = [];
			var splitString = $("#popup_send_numbers").val().split(",");
			for (var i = 0; i < splitString.length; i++) {
				try {
					numbers.push(verifyNumber(splitString[i]));
				} catch (numberError) {
					//TODO
					alert(numberError);
				}
			}
			var messageProto = new PushMessageContentProtobuf();
			messageProto.body = $("#popup_send_text").val();
			sendMessageToNumbers(numbers, messageProto,
				//TODO: Handle result
				function(thing) {console.log(thing);});
		});
	}
});
<commit_msg>tweak ui things a tiny bit<commit_after>/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

$('#inbox_link').click(function() {
	$('#inbox').show();
	$('#send').hide();
});
$('#send_link').click(function() {
	$('#inbox').hide();
	$('#send').show();
});

registerOnLoadFunction(function() {
	if (storage.getUnencrypted("number_id") === undefined) {
		chrome.tabs.create({url: "options.html"});
	} else {
		function fillMessages() {
			var MAX_MESSAGES_PER_CONVERSATION = 4;
			var MAX_CONVERSATIONS = 5;

			var conversations = [];

			var messageMap = getMessageMap();
			for (conversation in messageMap) {
				var messages = messageMap[conversation];
				messages.sort(function(a, b) { return b.timestamp - a.timestamp; });
				conversations[conversations.length] = messages;
			}

			conversations.sort(function(a, b) { return b[0].timestamp - a[0].timestamp });

			var ul = $('#conversations');
			ul.html('');
			for (var i = 0; i < MAX_CONVERSATIONS && i < conversations.length; i++) {
				var conversation = conversations[i];
				var messages = $('
');
				for (var j = 0; j < MAX_MESSAGES_PER_CONVERSATION && j < conversation.length; j++) {
					var message = conversation[j];
					$('
').
						append($('
')).
						append($('
').
							append($('').text(message.message)).
							append($('').text("From: " + message.sender + ", at: " + timestampToHumanReadable(message.timestamp)))
						).appendTo(messages);
				}
				var button = $('<button id="button' + i + '">').text('Send');
				var input = $('<input id="text' + i + '">');
				$('
').
					append(messages).
					append($("<form class='container'>").append(input).append(button)).
					appendTo(ul);
				button.click(function() {
					button.attr("disabled", "disabled");
					button.text("Sending");

					var sendDestinations = [conversation[0].sender];
					if (conversation[0].group)
						sendDestinations = conversation[0].group.members;

					var messageProto = new PushMessageContentProtobuf();
					messageProto.body = input.val();

					sendMessageToNumbers(sendDestinations, messageProto, function(result) {
						console.log(result);
						button.removeAttr("disabled");
						button.text("Send");
						input.val("");
					});
				});
			}
		}

		$(window).bind('storage', function(e) {
			console.log("Got localStorage update for key " + e.key);
			if (event.key == "emessageMap")//TODO: Fix when we get actual encryption
				fillMessages();
		});
		fillMessages();
		$('.my-number').text(storage.getUnencrypted("number_id").split(".")[0]);
		storage.putUnencrypted("unreadCount", 0);
		chrome.browserAction.setBadgeText({text: ""});
		$("#me").click(function() {
			$('#popup_send_numbers').val($('.my-number').text());
		});

		$("#popup_send_button").click(function() {
			var numbers = [];
			var splitString = $("#popup_send_numbers").val().split(",");
			for (var i = 0; i < splitString.length; i++) {
				try {
					numbers.push(verifyNumber(splitString[i]));
				} catch (numberError) {
					//TODO
					alert(numberError);
				}
			}
			var messageProto = new PushMessageContentProtobuf();
			messageProto.body = $("#popup_send_text").val();
			sendMessageToNumbers(numbers, messageProto,
				//TODO: Handle result
				function(thing) {console.log(thing);});
		});
	}
});
<|endoftext|>


<commit_before>/*jslint white: false, onevar: true, undef: true, nomen: true, eqeqeq: true, strict: true */
/*global $, jQuery, chrome, document */
"use strict";

QUERY = function() {

    /**
     * Object with user-friendly names for the modifiers.
     */
    var modifiers = {
        name : 'n',
        symbol : 's',
        last_price : 'l1',
        last_time : 't1',
        percent_change : 'p2',
        change : 'c1'
    };

    // Credit to http://stackoverflow.com/questions/1293147/javascript-code-to-parse-csv-data
    function csvToArray( strData, strDelimiter ){
        var objPattern, arrData, arrMatches, strMatchedDelimiter, strMatchedValue;

        // Check to see if the delimiter is defined. If not,
        // then default to comma.
        strDelimiter = (strDelimiter || ",");

        // Create a regular expression to parse the CSV values.
        objPattern = new RegExp(
            (
                // Delimiters.
                "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +

                // Quoted fields.
                "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +

                // Standard fields.
                "([^\"\\" + strDelimiter + "\\r\\n]*))"
            ),
            "gi"
        );

        // Create an array to hold our data. Give the array
        // a default empty first row.
        arrData = [[]];

        // Create an array to hold our individual pattern
        // matching groups.
        arrMatches = null;

        // Keep looping over the regular expression matches
        // until we can no longer find a match.
        while ((arrMatches = objPattern.exec( strData ))){

            // Get the delimiter that was found.
            strMatchedDelimiter = arrMatches[ 1 ];

            // Check to see if the given delimiter has a length
            // (is not the start of string) and if it matches
            // field delimiter. If id does not, then we know
            // that this delimiter is a row delimiter.
            if (
                strMatchedDelimiter.length &&
                    (strMatchedDelimiter !== strDelimiter)
            ){

                // Since we have reached a new row of data,
                // add an empty row to our data array.
                arrData.push( [] );

            }

            // Now that we have our delimiter out of the way,
            // let's check to see which kind of value we
            // captured (quoted or unquoted).
            if (arrMatches[ 2 ]){

                // We found a quoted value. When we capture
                // this value, unescape any double quotes.
                strMatchedValue = arrMatches[ 2 ].replace(
                    new RegExp( "\"\"", "g" ),
                    "\""
                );

            } else {
                // We found a non-quoted value.
                strMatchedValue = arrMatches[ 3 ];
            }

            // Now that we have our value string, let's add
            // it to the data array.
            arrData[ arrData.length - 1 ].push( strMatchedValue );
        }

        // Return the parsed data.
        return( arrData );
    }

    return {
        /**
         * Builds the modifiers string from the given arguments. The
         * method accept any number of arguments. Each of them is used as
         * a key in the modifiers object to find the corresponding option.
         *
         * @return {String} the modifiers as a string
         */
        options : function() {
            return $.map(arguments, function(option) {
                             return modifiers[option];
                         })
                .join('');
        },

        /**
         * Parses a csv formated string into an array of arrays.
         *
         * @param {String} strData the csv text.
         * @param {String} strDelimiter The delimiter. If no value is
         * provided, comma is used.
         */
        parseCsv : csvToArray,

        /**
         * Builds a query string with the given stocks and modifiers.
         *
         * @param {Array} stocks the list of stocks. It can be given
         * as a list of string ids, or as a list of objects with an id attribute
         * @param {String} options the modifiers to use, as returned
         * by options
         * @return {String} the url to use for the query
         */
        build : function(stocks, options) {
            var idArray;

            idArray = $.map(stocks,
                            function(stock) {
                                if (typeof(stock) == 'string') {
                                    return stock;
                                } else if (typeof(stock) == 'object' && stock.hasOwnProperty('id')) {
                                    return stock.id;
                                } else {
                                    throw {
                                        name: 'TypeError',
                                        message: 'Object cannot be interpreted as a stock'
                                    };
                                }
                            });

            return "http://download.finance.yahoo.com/d/quotes.csv?s=" +
                idArray.join("+") +
                "&f=" +
                options;
        },

        /**
         * Executes the request asynchronously, using a callback when the request returns.
         * @param {String} url the url of the request, as returned by build
         * @param {Function} callback the callback to use when the
         * request returns
         */
        send : function(url, callback) {
            $.get(url, callback);
        }
    };

}();
<commit_msg>fix undefined ref error<commit_after>/*jslint white: false, onevar: true, undef: true, nomen: true, eqeqeq: true, strict: true */
/*global $, jQuery, chrome, document */
"use strict";

var QUERY = function() {

    /**
     * Object with user-friendly names for the modifiers.
     */
    var modifiers = {
        name : 'n',
        symbol : 's',
        last_price : 'l1',
        last_time : 't1',
        percent_change : 'p2',
        change : 'c1'
    };

    // Credit to http://stackoverflow.com/questions/1293147/javascript-code-to-parse-csv-data
    function csvToArray( strData, strDelimiter ){
        var objPattern, arrData, arrMatches, strMatchedDelimiter, strMatchedValue;

        // Check to see if the delimiter is defined. If not,
        // then default to comma.
        strDelimiter = (strDelimiter || ",");

        // Create a regular expression to parse the CSV values.
        objPattern = new RegExp(
            (
                // Delimiters.
                "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +

                // Quoted fields.
                "(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|" +

                // Standard fields.
                "([^\"\\" + strDelimiter + "\\r\\n]*))"
            ),
            "gi"
        );

        // Create an array to hold our data. Give the array
        // a default empty first row.
        arrData = [[]];

        // Create an array to hold our individual pattern
        // matching groups.
        arrMatches = null;

        // Keep looping over the regular expression matches
        // until we can no longer find a match.
        while ((arrMatches = objPattern.exec( strData ))){

            // Get the delimiter that was found.
            strMatchedDelimiter = arrMatches[ 1 ];

            // Check to see if the given delimiter has a length
            // (is not the start of string) and if it matches
            // field delimiter. If id does not, then we know
            // that this delimiter is a row delimiter.
            if (
                strMatchedDelimiter.length &&
                    (strMatchedDelimiter !== strDelimiter)
            ){

                // Since we have reached a new row of data,
                // add an empty row to our data array.
                arrData.push( [] );

            }

            // Now that we have our delimiter out of the way,
            // let's check to see which kind of value we
            // captured (quoted or unquoted).
            if (arrMatches[ 2 ]){

                // We found a quoted value. When we capture
                // this value, unescape any double quotes.
                strMatchedValue = arrMatches[ 2 ].replace(
                    new RegExp( "\"\"", "g" ),
                    "\""
                );

            } else {
                // We found a non-quoted value.
                strMatchedValue = arrMatches[ 3 ];
            }

            // Now that we have our value string, let's add
            // it to the data array.
            arrData[ arrData.length - 1 ].push( strMatchedValue );
        }

        // Return the parsed data.
        return( arrData );
    }

    return {
        /**
         * Builds the modifiers string from the given arguments. The
         * method accept any number of arguments. Each of them is used as
         * a key in the modifiers object to find the corresponding option.
         *
         * @return {String} the modifiers as a string
         */
        options : function() {
            return $.map(arguments, function(option) {
                             return modifiers[option];
                         })
                .join('');
        },

        /**
         * Parses a csv formated string into an array of arrays.
         *
         * @param {String} strData the csv text.
         * @param {String} strDelimiter The delimiter. If no value is
         * provided, comma is used.
         */
        parseCsv : csvToArray,

        /**
         * Builds a query string with the given stocks and modifiers.
         *
         * @param {Array} stocks the list of stocks. It can be given
         * as a list of string ids, or as a list of objects with an id attribute
         * @param {String} options the modifiers to use, as returned
         * by options
         * @return {String} the url to use for the query
         */
        build : function(stocks, options) {
            var idArray;

            idArray = $.map(stocks,
                            function(stock) {
                                if (typeof(stock) == 'string') {
                                    return stock;
                                } else if (typeof(stock) == 'object' && stock.hasOwnProperty('id')) {
                                    return stock.id;
                                } else {
                                    throw {
                                        name: 'TypeError',
                                        message: 'Object cannot be interpreted as a stock'
                                    };
                                }
                            });

            return "http://download.finance.yahoo.com/d/quotes.csv?s=" +
                idArray.join("+") +
                "&f=" +
                options;
        },

        /**
         * Executes the request asynchronously, using a callback when the request returns.
         * @param {String} url the url of the request, as returned by build
         * @param {Function} callback the callback to use when the
         * request returns
         */
        send : function(url, callback) {
            $.get(url, callback);
        }
    };

}();
<|endoftext|>