<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 assert = require('assert');
var inherits = require('util').inherits;
var EventEmitter = require('./lib/event_emitter');
var net = require('net');

var TChannelConnection = require('./connection');
var errors = require('./errors');
var Request = require('./request');

var DEFAULT_REPORT_INTERVAL = 1000;

function TChannelPeer(channel, hostPort, options) {
    if (!(this instanceof TChannelPeer)) {
        return new TChannelPeer(channel, hostPort, options);
    }
    var self = this;
    EventEmitter.call(self);

    self.stateChangedEvent = self.defineEvent('stateChanged');
    self.allocConnectionEvent = self.defineEvent('allocConnection');

    assert(hostPort !== '0.0.0.0:0', 'Cannot create ephemeral peer');

    self.channel = channel;
    self.logger = self.channel.logger;
    self.timers = self.channel.timers;
    self.random = self.channel.random;
    self.options = options || {};
    self.hostPort = hostPort;
    self.connections = [];
    self.pendingIdentified = 0;
    self.heapElements = [];
    self.handler = new PreferOutgoingHandler(self);

    self.reportInterval = self.options.reportInterval || DEFAULT_REPORT_INTERVAL;
    if (self.reportInterval > 0 && self.channel.emitConnectionMetrics) {
        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }

    function onReport() {
        if (!self.hostPort) {
            return;
        }

        var count = self.countConnections('out');
        if (self.channel.emitConnectionMetrics) {
            self.channel.connectionsActiveStat.update(count, {
                'host-port': self.channel.hostPort,
                'peer-host-port': self.hostPort
            });
        }

        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }
}

inherits(TChannelPeer, EventEmitter);

TChannelPeer.prototype.invalidateScore = function invalidateScore() {
    var self = this;

    if (!self.heapElements.length) {
        return;
    }

    var score = self.handler.getScore();
    for (var i = 0; i < self.heapElements.length; i++) {
        var el = self.heapElements[i];
        el.rescore(score);
    }
};

TChannelPeer.prototype.isConnected = function isConnected(direction, identified) {
    var self = this;

    if (identified === undefined) identified = true;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (direction && conn.direction !== direction) {
            continue;
        } else if (conn.closing) {
            continue;
        } else if (conn.remoteName !== null || !identified) {
            return true;
        }
    }

    return false;
};

TChannelPeer.prototype.close = function close(callback) {
    var self = this;

    if (self.reportTimer) {
        self.timers.clearTimeout(self.reportTimer);
        self.reportTimer = null;
    }

    var counter = self.connections.length;
    if (counter) {
        self.connections.forEach(function eachConn(conn) {
            conn.close(onClose);
        });
    } else {
        callback(null);
    }
    function onClose() {
        if (--counter <= 0) {
            if (counter < 0) {
                self.logger.error('closed more peer sockets than expected', {
                    counter: counter
                });
            }
            callback(null);
        }
    }
};

TChannelPeer.prototype.getInConnection = function getInConnection() {
    var self = this;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (!conn.closing) return conn;
    }
    return null;
};

TChannelPeer.prototype.getOutConnection = function getOutConnection(preferIdentified) {
    var self = this;
    var candidate = null;
    for (var i = self.connections.length - 1; i >= 0; i--) {
        var conn = self.connections[i];
        if (conn.closing) continue;
        if (!preferIdentified) return conn; // user doesn't care, take last outgoing
        if (conn.remoteName) return conn; // user wanted an identified channel, and we found one
        if (!candidate) candidate = conn; // we'll fallback to returning this if we can't find an identified one
    }
    return candidate;
};

TChannelPeer.prototype.getIdentifiedOutConnection = function getIdentifiedOutConnection() {
    var self = this;
    return self.getOutConnection(true);
};

TChannelPeer.prototype.countConnections = function countConnections(direction) {
    var self = this;
    if (!direction) {
        return self.connections.length;
    }

    var count = 0;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (conn.direction === direction) {
            count++;
        }
    }

    return count;
};

TChannelPeer.prototype.connect = function connect(outOnly) {
    var self = this;
    var conn = self.getIdentifiedOutConnection();
    if (!conn || (outOnly && conn.direction !== 'out')) {
        var socket = self.makeOutSocket();
        conn = self.makeOutConnection(socket);
        self.addConnection(conn);
    }
    return conn;
};

TChannelPeer.prototype.waitForIdentified =
function waitForIdentified(callback) {
    var self = this;

    var conn = self.connect();

    if (conn.closing) {
        callback(conn.closeError);
    } else if (conn.remoteName) {
        callback(null);
    } else {
        self._waitForIdentified(conn, callback);
    }
};

TChannelPeer.prototype._waitForIdentified =
function _waitForIdentified(conn, callback) {
    var self = this;

    self.pendingIdentified++;
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);
    conn.identifiedEvent.on(onIdentified);
    self.invalidateScore();

    function onConnectionError(err) {
        finish(err);
    }

    function onConnectionClose(err) {
        finish(err);
    }

    function onIdentified() {
        finish(null);
    }

    function finish(err) {
        self.pendingIdentified = 0;
        conn.errorEvent.removeListener(onConnectionError);
        conn.closeEvent.removeListener(onConnectionClose);
        conn.identifiedEvent.removeListener(onIdentified);
        self.invalidateScore();
        callback(err);
    }
};

TChannelPeer.prototype.request = function peerRequest(options) {
    var self = this;
    options.timeout = options.timeout || Request.defaultTimeout;
    return self.connect().request(options);
};

TChannelPeer.prototype.addConnection = function addConnection(conn) {
    var self = this;
    // TODO: first approx alert for self.connections.length > 2
    // TODO: second approx support pruning
    if (conn.direction === 'out') {
        self.connections.push(conn);
    } else {
        self.connections.unshift(conn);
    }
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);

    self._maybeInvalidateScore();
    if (!conn.remoteName) {
        // TODO: could optimize if handler had a way of saying "would a new
        // identified connection change your Tier?"
        conn.identifiedEvent.on(onIdentified);
    }

    return conn;

    function onIdentified() {
        conn.identifiedEvent.removeListener(onIdentified);
        self._maybeInvalidateScore();
    }

    function onConnectionError(err) {
        removeConnection(err);
    }

    function onConnectionClose() {
        removeConnection(null);
    }

    function removeConnection(err) {
        conn.closeEvent.removeListener(onConnectionClose);
        conn.errorEvent.removeListener(onConnectionError);
        conn.identifiedEvent.removeListener(onIdentified);
        if (err) {
            var loggerInfo = {
                error: err,
                direction: conn.direction,
                remoteName: conn.remoteName,
                socketRemoteAddr: conn.socketRemoteAddr
            };

            var codeName = errors.classify(err);
            if (codeName === 'Timeout') {
                self.logger.warn('Got a connection error', loggerInfo);
            } else {
                self.logger.error('Got an unexpected connection error', loggerInfo);
            }
        }

        self.removeConnection(conn);
    }
};

TChannelPeer.prototype.removeConnection = function removeConnection(conn) {
    var self = this;

    var ret = null;

    var index = self.connections ? self.connections.indexOf(conn) : -1;
    if (index !== -1) {
        ret = self.connections.splice(index, 1)[0];
    }

    self._maybeInvalidateScore();

    return ret;
};

TChannelPeer.prototype.makeOutSocket = function makeOutSocket() {
    var self = this;
    var parts = self.hostPort.split(':');
    assert(parts.length === 2, 'invalid destination');
    var host = parts[0];
    var port = parts[1];
    assert(host !== '0.0.0.0', 'cannot connect to ephemeral peer');
    assert(port !== '0', 'cannot connect to dynamic port');
    var socket = net.createConnection({host: host, port: port});
    return socket;
};

TChannelPeer.prototype.makeOutConnection = function makeOutConnection(socket) {
    var self = this;
    var chan = self.channel.topChannel || self.channel;
    var conn = new TChannelConnection(chan, socket, 'out', self.hostPort);
    self.allocConnectionEvent.emit(self, conn);
    return conn;
};

TChannelPeer.prototype.outPendingWeightedRandom = function outPendingWeightedRandom() {
    // Returns a score in the range from 0 to 1, where it is preferable to use
    // a peer with a higher score over one with a lower score.
    // This range is divided among an infinite set of subranges corresponding
    // to peers with the same number of pending requests.
    // So, the range (1/2, 1) is reserved for peers with 0 pending connections.
    // The range (1/4, 1/2) is reserved for peers with 1 pending connections.
    // The range (1/8, 1/4) is reserved for peers with 2 pending connections.
    // Ad nauseam.
    // Within each equivalence class, each peer receives a uniform random
    // value.
    //
    // The previous score was a weighted random variable:
    //   random() ** (1 + pending)
    // This had the attribute that a less loaded peer was merely more likely to
    // be chosen over a more loaded peer.
    // We observed with the introduction of a heap, that a less favored peer
    // would have its score less frequently re-evaluated.
    // An emergent behavior was that scores would, over time, be squeezed
    // toward zero and the least favored peer would remain the least favored
    // for ever increasing durations.
    //
    // This remains true with this algorithm, within each equivalence class.
    var self = this;
    var pending = self.pendingIdentified + self.countOutPending();
    var max = Math.pow(0.5, pending);
    var min = max / 2;
    var diff = max - min;
    return min + diff * self.random();
};

TChannelPeer.prototype.countOutPending = function countOutPending() {
    var self = this;
    var pending = 0;
    for (var index = 0; index < self.connections.length; index++) {
        var connPending = self.connections[index].ops.getPending();

        pending += connPending.out;
    }
    return pending;
};

// TODO: on connection #getScore impacting event
// - on identified

// Called on connection change event
TChannelPeer.prototype._maybeInvalidateScore = function _maybeInvalidateScore() {
    var self = this;

    if (self.handler.getTier() !== self.handler.lastTier) {
        self.invalidateScore();
    }
};

TChannelPeer.prototype.getScore = function getScore() {
    var self = this;
    return self.handler.getScore();
};

var TIER_UNCONNECTED = 0;
var TIER_ONLY_INCOMING = 1;
var TIER_FRESH_OUTGOING = 2;
var TIER_READY_OUTGOING = 3;

function PreferOutgoingHandler(peer) {
    var self = this;

    self.peer = peer;
    self.lastTier = self.getTier();
}

PreferOutgoingHandler.prototype.getTier = function getTier() {
    var self = this;

    var inconn = self.peer.getInConnection();
    var outconn = self.peer.getIdentifiedOutConnection();

    if (!inconn && !outconn) {
        return TIER_UNCONNECTED;
    } else if (!outconn || outconn.direction !== 'out') {
        return TIER_ONLY_INCOMING;
    } else if (outconn.remoteName === null) {
        return TIER_FRESH_OUTGOING;
    } else {
        return TIER_READY_OUTGOING;
    }
};

PreferOutgoingHandler.prototype.getScore = function getScore() {
    var self = this;

    // space:
    //   [0.1, 0.4)  peers with no identified outgoing connection
    //   [0.4, 1.0)  identified outgoing connections
    var random = self.peer.outPendingWeightedRandom();
    var qos = self.getTier();
    self.lastTier = qos;
    switch (qos) {
        case TIER_ONLY_INCOMING:
            if (!self.peer.channel.destroyed) {
                self.peer.connect();
            }
            /* falls through */
        case TIER_UNCONNECTED:
            /* falls through */
        case TIER_FRESH_OUTGOING:
            return 0.1 + random * 0.3;
        case TIER_READY_OUTGOING:
            return 0.4 + random * 0.6;
    }
};

module.exports = TChannelPeer;
<commit_msg>enforce outgoing connection<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 assert = require('assert');
var inherits = require('util').inherits;
var EventEmitter = require('./lib/event_emitter');
var net = require('net');

var TChannelConnection = require('./connection');
var errors = require('./errors');
var Request = require('./request');

var DEFAULT_REPORT_INTERVAL = 1000;

function TChannelPeer(channel, hostPort, options) {
    if (!(this instanceof TChannelPeer)) {
        return new TChannelPeer(channel, hostPort, options);
    }
    var self = this;
    EventEmitter.call(self);

    self.stateChangedEvent = self.defineEvent('stateChanged');
    self.allocConnectionEvent = self.defineEvent('allocConnection');

    assert(hostPort !== '0.0.0.0:0', 'Cannot create ephemeral peer');

    self.channel = channel;
    self.logger = self.channel.logger;
    self.timers = self.channel.timers;
    self.random = self.channel.random;
    self.options = options || {};
    self.hostPort = hostPort;
    self.connections = [];
    self.pendingIdentified = 0;
    self.heapElements = [];
    self.handler = new PreferOutgoingHandler(self);

    self.reportInterval = self.options.reportInterval || DEFAULT_REPORT_INTERVAL;
    if (self.reportInterval > 0 && self.channel.emitConnectionMetrics) {
        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }

    function onReport() {
        if (!self.hostPort) {
            return;
        }

        var count = self.countConnections('out');
        if (self.channel.emitConnectionMetrics) {
            self.channel.connectionsActiveStat.update(count, {
                'host-port': self.channel.hostPort,
                'peer-host-port': self.hostPort
            });
        }

        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }
}

inherits(TChannelPeer, EventEmitter);

TChannelPeer.prototype.invalidateScore = function invalidateScore() {
    var self = this;

    if (!self.heapElements.length) {
        return;
    }

    var score = self.handler.getScore();
    for (var i = 0; i < self.heapElements.length; i++) {
        var el = self.heapElements[i];
        el.rescore(score);
    }
};

TChannelPeer.prototype.isConnected = function isConnected(direction, identified) {
    var self = this;

    if (identified === undefined) identified = true;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (direction && conn.direction !== direction) {
            continue;
        } else if (conn.closing) {
            continue;
        } else if (conn.remoteName !== null || !identified) {
            return true;
        }
    }

    return false;
};

TChannelPeer.prototype.close = function close(callback) {
    var self = this;

    if (self.reportTimer) {
        self.timers.clearTimeout(self.reportTimer);
        self.reportTimer = null;
    }

    var counter = self.connections.length;
    if (counter) {
        self.connections.forEach(function eachConn(conn) {
            conn.close(onClose);
        });
    } else {
        callback(null);
    }
    function onClose() {
        if (--counter <= 0) {
            if (counter < 0) {
                self.logger.error('closed more peer sockets than expected', {
                    counter: counter
                });
            }
            callback(null);
        }
    }
};

TChannelPeer.prototype.getInConnection = function getInConnection() {
    var self = this;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (!conn.closing) return conn;
    }
    return null;
};

TChannelPeer.prototype.getOutConnection = function getOutConnection(preferIdentified) {
    var self = this;
    var candidate = null;
    for (var i = self.connections.length - 1; i >= 0; i--) {
        var conn = self.connections[i];
        if (conn.closing) continue;
        if (!preferIdentified) return conn; // user doesn't care, take last outgoing
        if (conn.remoteName) return conn; // user wanted an identified channel, and we found one
        if (!candidate) candidate = conn; // we'll fallback to returning this if we can't find an identified one
    }
    return candidate;
};

TChannelPeer.prototype.getIdentifiedOutConnection = function getIdentifiedOutConnection() {
    var self = this;
    return self.getOutConnection(true);
};

TChannelPeer.prototype.countConnections = function countConnections(direction) {
    var self = this;
    if (!direction) {
        return self.connections.length;
    }

    var count = 0;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (conn.direction === direction) {
            count++;
        }
    }

    return count;
};

TChannelPeer.prototype.connect = function connect(outOnly) {
    var self = this;
    var conn = self.getIdentifiedOutConnection();
    if (!conn || (outOnly && conn.direction !== 'out')) {
        var socket = self.makeOutSocket();
        conn = self.makeOutConnection(socket);
        self.addConnection(conn);
    }
    return conn;
};

TChannelPeer.prototype.waitForIdentified =
function waitForIdentified(callback) {
    var self = this;

    var conn = self.connect();

    if (conn.closing) {
        callback(conn.closeError);
    } else if (conn.remoteName) {
        callback(null);
    } else {
        self._waitForIdentified(conn, callback);
    }
};

TChannelPeer.prototype._waitForIdentified =
function _waitForIdentified(conn, callback) {
    var self = this;

    self.pendingIdentified++;
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);
    conn.identifiedEvent.on(onIdentified);
    self.invalidateScore();

    function onConnectionError(err) {
        finish(err);
    }

    function onConnectionClose(err) {
        finish(err);
    }

    function onIdentified() {
        finish(null);
    }

    function finish(err) {
        self.pendingIdentified = 0;
        conn.errorEvent.removeListener(onConnectionError);
        conn.closeEvent.removeListener(onConnectionClose);
        conn.identifiedEvent.removeListener(onIdentified);
        self.invalidateScore();
        callback(err);
    }
};

TChannelPeer.prototype.request = function peerRequest(options) {
    var self = this;
    options.timeout = options.timeout || Request.defaultTimeout;
    return self.connect().request(options);
};

TChannelPeer.prototype.addConnection = function addConnection(conn) {
    var self = this;
    // TODO: first approx alert for self.connections.length > 2
    // TODO: second approx support pruning
    if (conn.direction === 'out') {
        self.connections.push(conn);
    } else {
        self.connections.unshift(conn);
    }
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);

    self._maybeInvalidateScore();
    if (!conn.remoteName) {
        // TODO: could optimize if handler had a way of saying "would a new
        // identified connection change your Tier?"
        conn.identifiedEvent.on(onIdentified);
    }

    return conn;

    function onIdentified() {
        conn.identifiedEvent.removeListener(onIdentified);
        self._maybeInvalidateScore();
    }

    function onConnectionError(err) {
        removeConnection(err);
    }

    function onConnectionClose() {
        removeConnection(null);
    }

    function removeConnection(err) {
        conn.closeEvent.removeListener(onConnectionClose);
        conn.errorEvent.removeListener(onConnectionError);
        conn.identifiedEvent.removeListener(onIdentified);
        if (err) {
            var loggerInfo = {
                error: err,
                direction: conn.direction,
                remoteName: conn.remoteName,
                socketRemoteAddr: conn.socketRemoteAddr
            };

            var codeName = errors.classify(err);
            if (codeName === 'Timeout') {
                self.logger.warn('Got a connection error', loggerInfo);
            } else {
                self.logger.error('Got an unexpected connection error', loggerInfo);
            }
        }

        self.removeConnection(conn);
    }
};

TChannelPeer.prototype.removeConnection = function removeConnection(conn) {
    var self = this;

    var ret = null;

    var index = self.connections ? self.connections.indexOf(conn) : -1;
    if (index !== -1) {
        ret = self.connections.splice(index, 1)[0];
    }

    self._maybeInvalidateScore();

    return ret;
};

TChannelPeer.prototype.makeOutSocket = function makeOutSocket() {
    var self = this;
    var parts = self.hostPort.split(':');
    assert(parts.length === 2, 'invalid destination');
    var host = parts[0];
    var port = parts[1];
    assert(host !== '0.0.0.0', 'cannot connect to ephemeral peer');
    assert(port !== '0', 'cannot connect to dynamic port');
    var socket = net.createConnection({host: host, port: port});
    return socket;
};

TChannelPeer.prototype.makeOutConnection = function makeOutConnection(socket) {
    var self = this;
    var chan = self.channel.topChannel || self.channel;
    var conn = new TChannelConnection(chan, socket, 'out', self.hostPort);
    self.allocConnectionEvent.emit(self, conn);
    return conn;
};

TChannelPeer.prototype.outPendingWeightedRandom = function outPendingWeightedRandom() {
    // Returns a score in the range from 0 to 1, where it is preferable to use
    // a peer with a higher score over one with a lower score.
    // This range is divided among an infinite set of subranges corresponding
    // to peers with the same number of pending requests.
    // So, the range (1/2, 1) is reserved for peers with 0 pending connections.
    // The range (1/4, 1/2) is reserved for peers with 1 pending connections.
    // The range (1/8, 1/4) is reserved for peers with 2 pending connections.
    // Ad nauseam.
    // Within each equivalence class, each peer receives a uniform random
    // value.
    //
    // The previous score was a weighted random variable:
    //   random() ** (1 + pending)
    // This had the attribute that a less loaded peer was merely more likely to
    // be chosen over a more loaded peer.
    // We observed with the introduction of a heap, that a less favored peer
    // would have its score less frequently re-evaluated.
    // An emergent behavior was that scores would, over time, be squeezed
    // toward zero and the least favored peer would remain the least favored
    // for ever increasing durations.
    //
    // This remains true with this algorithm, within each equivalence class.
    var self = this;
    var pending = self.pendingIdentified + self.countOutPending();
    var max = Math.pow(0.5, pending);
    var min = max / 2;
    var diff = max - min;
    return min + diff * self.random();
};

TChannelPeer.prototype.countOutPending = function countOutPending() {
    var self = this;
    var pending = 0;
    for (var index = 0; index < self.connections.length; index++) {
        var connPending = self.connections[index].ops.getPending();

        pending += connPending.out;
    }
    return pending;
};

// TODO: on connection #getScore impacting event
// - on identified

// Called on connection change event
TChannelPeer.prototype._maybeInvalidateScore = function _maybeInvalidateScore() {
    var self = this;

    if (self.handler.getTier() !== self.handler.lastTier) {
        self.invalidateScore();
    }
};

TChannelPeer.prototype.getScore = function getScore() {
    var self = this;
    return self.handler.getScore();
};

var TIER_UNCONNECTED = 0;
var TIER_ONLY_INCOMING = 1;
var TIER_FRESH_OUTGOING = 2;
var TIER_READY_OUTGOING = 3;

function PreferOutgoingHandler(peer) {
    var self = this;

    self.peer = peer;
    self.lastTier = self.getTier();
}

PreferOutgoingHandler.prototype.getTier = function getTier() {
    var self = this;

    var inconn = self.peer.getInConnection();
    var outconn = self.peer.getIdentifiedOutConnection();

    if (!inconn && !outconn) {
        return TIER_UNCONNECTED;
    } else if (!outconn || outconn.direction !== 'out') {
        return TIER_ONLY_INCOMING;
    } else if (outconn.remoteName === null) {
        return TIER_FRESH_OUTGOING;
    } else {
        return TIER_READY_OUTGOING;
    }
};

PreferOutgoingHandler.prototype.getScore = function getScore() {
    var self = this;

    // space:
    //   [0.1, 0.4)  peers with no identified outgoing connection
    //   [0.4, 1.0)  identified outgoing connections
    var random = self.peer.outPendingWeightedRandom();
    var qos = self.getTier();
    self.lastTier = qos;
    switch (qos) {
        case TIER_ONLY_INCOMING:
            if (!self.peer.channel.destroyed) {
                self.peer.connect(true);
            }
            /* falls through */
        case TIER_UNCONNECTED:
            /* falls through */
        case TIER_FRESH_OUTGOING:
            return 0.1 + random * 0.3;
        case TIER_READY_OUTGOING:
            return 0.4 + random * 0.6;
    }
};

module.exports = TChannelPeer;
<|endoftext|>


<commit_before>javascript:
(function(){

var myPrefix = wpTextbox1.value.substring(0, wpTextbox1.selectionStart);
var mySuffix = wpTextbox1.value.substring(wpTextbox1.selectionEnd);
var list = myPrefix.match(/\[\[(User|User talk|用戶|用户|用戶討論|用户讨论):[^|\]]+[^\]]*\]\]/gi);
var username = "";
for (var i = list.length - 1; i >= 0; i--) {
	var temp = list[i].match(/\[\[(?:User|User talk|用戶|用户|用戶討論|用户讨论):([^|\]]+)[^\]]*\]\]/i);
	if (temp !== null && temp[1] != "A2093064") {
		username = temp[1];
		break;
	}
}
var indent = myPrefix.match(/^([\:*]+)/mg);
if (indent === null) {
	indent = ":";
} else {
	indent = indent[indent.length-1]+indent[indent.length-1][0];
}
wpTextbox1.value = myPrefix + indent+ "{{ping|"+username+"}}" + mySuffix;

})();
<commit_msg>ping match fix #<commit_after>javascript:
(function(){

var myPrefix = wpTextbox1.value.substring(0, wpTextbox1.selectionStart);
var mySuffix = wpTextbox1.value.substring(wpTextbox1.selectionEnd);
var list = myPrefix.match(/\[\[(User|User talk|用戶|用户|用戶討論|用户讨论):[^|\]#]+[^\]]*\]\]/gi);
var username = "";
for (var i = list.length - 1; i >= 0; i--) {
	var temp = list[i].match(/\[\[(?:User|User talk|用戶|用户|用戶討論|用户讨论):([^|\]#]+)[^\]]*\]\]/i);
	if (temp !== null && temp[1] != "A2093064") {
		username = temp[1];
		break;
	}
}
var indent = myPrefix.match(/^([\:*]+)/mg);
if (indent === null) {
	indent = ":";
} else {
	indent = indent[indent.length-1]+indent[indent.length-1][0];
}
wpTextbox1.value = myPrefix + indent+ "{{ping|"+username+"}}" + mySuffix;

})();
<|endoftext|>


<commit_before>var norma = require('norma');
var request = require('request');
var debug = require('debug')('waif:pipe');
var Uri   = require('./state/uri');

module.exports = function(config) {
  var service = this.service;

  var args = norma('url:s, rest:.*', arguments);
  debug('service %s piped to %s', service.name, args.url);

  return function(req, res, next) {
    debug('service %s piping to %s', service.name, req.url);

    var proxyUrl = args.url;
    Object.keys(req.params).forEach(function(key) {
      proxyUrl = proxyUrl.replace(':' + key, req.params[key]);
    });

    var uri = new Uri();
    uri.set(proxyUrl);

    console.log(proxyUrl)

    var proxy = request(uri.requestUrl(proxyUrl));
    req.pipe(proxy);
    proxy.pipe(res);
  };
};
<commit_msg>Removed logging<commit_after>var norma = require('norma');
var request = require('request');
var debug = require('debug')('waif:pipe');
var Uri   = require('./state/uri');

module.exports = function(config) {
  var service = this.service;

  var args = norma('url:s, rest:.*', arguments);
  debug('service %s piped to %s', service.name, args.url);

  return function(req, res, next) {
    debug('service %s piping to %s', service.name, req.url);

    var proxyUrl = args.url;
    Object.keys(req.params).forEach(function(key) {
      proxyUrl = proxyUrl.replace(':' + key, req.params[key]);
    });

    var uri = new Uri();
    uri.set(proxyUrl);

    var proxy = request(uri.requestUrl(proxyUrl));
    req.pipe(proxy);
    proxy.pipe(res);
  };
};
<|endoftext|>


<commit_before>$(function() {
    "use strict";

    var opt = {
        'row': 1,
        'noedit': false,
        'nomask': false,
        'memory': null,
        'export': false,
        'data': null
    };

    var current_row = 0;
    var mouse_tracking;
    var touch_tracking = [];

    // http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
    function is_touch_device() {
        return !!('ontouchstart' in window) // works on most browsers
            || !!('onmsgesturechange' in window); // works on ie10
    }

    function get_data() {
        var data = "";
        for (var i = 1; i <= 16; ++i) {
            for (var j = 1; j <= 16; ++j) {
                var panel = $('#panel' + i + '-' + j);

                if (panel.hasClass('white')) {
                    data += '0';
                }
                else if (panel.hasClass('black')) {
                    data += '1';
                }
                else {
                    data += '.';
                }
            }
        }
        return data;
    }

    function set_data(data) {
        for (var i = 1; i <= 16; ++i) {
            for (var j = 1; j <= 16; ++j) {
                var panel = $('#panel' + i + '-' + j);

                var k = 16 * (i - 1) + (j - 1);
                if (data[k] === '0') {
                    panel.addClass("white invisible-text");
                    panel.removeClass("black");
                }
                else if (data[k] === '1') {
                    panel.addClass("black invisible-text");
                    panel.removeClass("white");
                }
                else {
                    panel.removeClass("white black invisible-text");
                }
            }
        }
    }

    function toggle_panel_color(panel) {
        if ($(panel).hasClass("black") || $(panel).hasClass("white")) {
            $(panel).toggleClass("black white");
        }
        else {
            $(panel).addClass("white invisible-text");
        }
        localStorage['resume_data'] = JSON.stringify({ 'row': current_row, 'data': get_data() });
    }

    function unmagnify_row(i, duration, delay) {
        duration /= 1000;
        delay /= 1000;
        var row = $('#row' + i);
        row.css({
            'transition-duration': '0.001s, ' + duration + 's, ' + duration + 's, ' + duration + 's, ' + duration + 's',
            'transition-delay': delay + 's'
        });
        row.removeClass('magnified');
    }

    function magnify_row(i, duration, delay) {
        duration /= 1000;
        delay /= 1000;
        var row = $('#row' + i);
        row.css({
            'transition-duration': '0.001s, ' + duration + 's, ' + duration + 's, ' + duration + 's, ' + duration + 's',
            'transition-delay': delay + 's'
        });
        row.addClass('magnified');
    }

    function move_focus(i) {
        var row = $('#row' + i);

        var unmasked_top = row.position().top;
        var unmasked_bottom = unmasked_top + row.outerHeight();

        var upper_mask = $('#upper-mask');
        var lower_mask = $('#lower-mask');
        upper_mask.css('transform', 'translate3d(0px, ' + (unmasked_top - upper_mask.outerHeight()) + 'px, 0px)');
        lower_mask.css('transform', 'translate3d(0px, ' + unmasked_bottom + 'px, 0px)');
    }

    function move_cursor_button(i) {
        var row = $('#row' + i);

        var unmasked_top = row.position().top;
        var unmasked_bottom = unmasked_top + row.outerHeight();

        var scaled_top = unmasked_top - 0.3 * row.outerHeight() / 2;
        var scaled_bottom = unmasked_bottom + 0.3 * row.outerHeight() / 2;
        var scaled_left = -0.3 * row.outerWidth() / 2;

        var space = 0;

        var cursor_panel = $('#cursor-panel');
        cursor_panel.css('transform',
            'translate3d(' +
                (scaled_left - 1.3 * 60 / 2 - cursor_panel.outerWidth() / 2) + 'px, ' +
                (unmasked_top + row.outerHeight() / 2 - cursor_panel.outerHeight() / 2 - space) + 'px, ' +
                '0)');
    }

    function initialize_mask_transformations() {
        var board_height = $('#board').outerHeight();

        $('#upper-mask').css('transform', 'translate3d(0px, ' + (-board_height) + 'px, 0px)');
        $('#lower-mask').css('transform', 'translate3d(0px, ' + board_height + 'px, 0px)');
    }

    function select_row(i) {
        unmagnify_row(current_row, 400, 0);
        current_row = i;
        move_focus(current_row);
        move_cursor_button(current_row);
        magnify_row(current_row, 400, 0);
        localStorage['resume_data'] = JSON.stringify({ 'row': current_row, 'data': get_data() });
    }

    function on_panel_mousedown(e) {
        mouse_tracking = { target: this };
        toggle_panel_color(this);
    }

    function on_panel_mousemove(e) {
        if (mouse_tracking && mouse_tracking.target !== this) {
            toggle_panel_color(this);
            mouse_tracking.target = this;
        }
    }

    function on_panel_mouseup(e) {
        mouse_tracking = null;
    }

    function on_panel_touchstart(e) {
        $.each(e.originalEvent.changedTouches, function(_, t) {
            var target = document.elementFromPoint(t.clientX, t.clientY);
            if ($(target).hasClass('panel')) {
                touch_tracking[t.identifier] = { target: target };
                toggle_panel_color(target);
            }
        });
    }

    function on_panel_touchmove(e) {
        $.each(e.originalEvent.changedTouches, function(_, t) {
            var target = document.elementFromPoint(t.clientX, t.clientY);
            if ($(target).hasClass('panel')) {
                if (touch_tracking[t.identifier] && touch_tracking[t.identifier].target !== target) {
                    toggle_panel_color(target);
                    touch_tracking[t.identifier].target = target;
                }
            }
        });
    }

    function on_panel_touchend(e) {
        $.each(e.originalEvent.changedTouches, function(_, t) {
            touch_tracking[t.identifier] = null;
        });
    }

    function change_query_string(qs, kvs) {
        $.each(kvs, function(key, value) {
            var re = new RegExp('([?&])' + key + '=[^&]+');
            if (qs.match(re)) {
                // The key is already used.
                qs = qs.replace(re, '$1' + key + '=' + value);
            }
            else {
                // The key isn't used.
                if (qs.match(/[?&]$/)) {
                    qs += key + '=' + value;
                }
                else if (qs.length > 0) {
                    qs += '&' + key + '=' + value;
                }
                else {
                    qs += '?' + key + '=' + value;
                }
            }
        });
        return qs;
    }

    function export_data() {
        var loc = window.location;
        loc.search = change_query_string(loc.search, { 'data': get_data() });
    }

    function make_thumbnail(data, scale) {
        var canvas = $('<canvas/>');
        canvas[0].width = scale * 16;
        canvas[0].height = scale * 16;
        var ctx = canvas[0].getContext('2d');
        for (var i = 0; i < 16; ++i) {
            for (var j = 0; j < 16; ++j) {
                var k = 16 * i + j;
                if (data[k] === '0') {
                    ctx.fillStyle = 'white';
                }
                else if (data[k] === '1') {
                    ctx.fillStyle = 'black';
                }
                else {
                    ctx.fillStyle = 'gray';
                }
                ctx.fillRect(scale * j, scale * i, scale, scale);
            }
        }
        return canvas[0].toDataURL();
    }

    // process options
    $.each(window.location.search.slice(1).split("&"), function(_, param) {
        var kv = param.split("=");

        switch (kv[0]) {
        case "row":
            opt.row = parseInt(kv[1]);
            break;
        case "noedit":
            opt.noedit = true;
            break;
        case "nomask":
            opt.nomask = true;
            break;
        case "memory":
            switch (kv[1]) {
            case 'save':
                opt.memory = 'save';
                break;
            case 'load':
                opt.memory = 'load';
                break;
            case 'clear':
                opt.memory = 'clear';
                break;
            }
            break;
        case "export":
            opt['export'] = true;
            break;
        case "data":
            opt.data = kv[1];
            break;
        }
    });

    // create the board
    for (var i = 1; i <= 16; ++i) {
        var row = $('

');
        for (var j = 1; j <= 16; ++j) {
            var cell = $('

');
            var panel = $('
?
');
            panel.appendTo(cell);
            cell.appendTo(row);
        }
        row.appendTo('#board');
        var num_str = (i < 10 ? '0' : '') + i;
        $('').appendTo('#board');
    }

    // memory
    switch (opt.memory) {
    case 'load':
        $('#load-panel').css('display', 'block');
        $('.load-button').each(function() {
            var n = parseInt($(this).attr("id").slice('load-button'.length));
            var scale = Math.floor($(this).width() / 16);
            $(this).css('background-image', 'url(' + make_thumbnail(localStorage[n] || '', scale) + ')');
        });
        $('.load-button').on('click', function() {
            var n = parseInt($(this).attr("id").slice('load-button'.length));
            set_data(localStorage[n] || '');
        });
        break;
    case 'save':
        $('#save-panel').css('display', 'block');
        $('.save-button').each(function() {
            var n = parseInt($(this).attr("id").slice('save-button'.length));
            var scale = Math.floor($(this).width() / 16);
            $(this).css('background-image', 'url(' + make_thumbnail(localStorage[n] || '', scale) + ')');
        });
        $('.save-button').on('click', function() {
            var n = parseInt($(this).attr("id").slice('save-button'.length));
            var scale = Math.floor($(this).width() / 16);
            var data = get_data();
            localStorage[n] = data;
            $(this).css('background-image', 'url(' + make_thumbnail(data, scale) + ')');
        });
        break;
    case 'clear':
        localStorage.clear();
        break;
    }

    if (opt.data !== null) {
        set_data(opt.data);
    }
    else if (typeof localStorage['resume_data'] !== 'undefined') {
        set_data(JSON.parse(localStorage['resume_data']).data);
    }

    if (opt['export']) {
        $('#export-button').css('display', 'block');
    }

    if (opt.nomask) {
        $('.mask').css('display', 'none');
        $('#prev-button, #next-button').css('display', 'none');
    }
    else {
        // masks
        initialize_mask_transformations();

        // initial focus
        if (opt.data !== null) {
            current_row = opt.row;
        }
        else if (typeof localStorage['resume_data'] !== 'undefined') {
            current_row = JSON.parse(localStorage['resume_data']).row;
        }
        else {
            current_row = opt.row;
        }
        move_focus(current_row, 400, 0);
        move_cursor_button(current_row, 400, 400);
        magnify_row(current_row, 400, 400);
    }

    // events
    if (is_touch_device()) {
        // disable the overscroll effect on touch-capable environments.
        // http://www.html5rocks.com/en/mobile/touch/
        // http://stackoverflow.com/questions/2890361/disable-scrolling-in-an-iphone-web-application
        $(document).on('touchmove', function(e) {
            e.preventDefault();
        });

        if (!opt.nomask) {
            $('#prev-button').on('touchstart', function() {
                if (current_row > 1) {
                    select_row(current_row - 1);
                }
            });
            $('#next-button').on('touchstart', function() {
                if (current_row < 16) {
                    select_row(current_row + 1);
                }
            });
        }

        if (!opt.noedit) {
            $(".panel").each(function() {
                $(this).on('touchstart', on_panel_touchstart);
                $(this).on('touchmove', on_panel_touchmove);
                $(this).on('touchend', on_panel_touchend);
            });
        }
    }
    else {
        if (!opt.nomask) {
            $('#prev-button').on('click', function() {
                if (current_row > 1) {
                    select_row(current_row - 1);
                }
            });
            $('#next-button').on('click', function() {
                if (current_row < 16) {
                    select_row(current_row + 1);
                }
            });
            $(document).on('wheel mousewheel DOMMouseScroll', function(e) {
                var delta =   e.originalEvent.deltaY       // 'wheel' event
                          || -e.originalEvent.wheelDeltaY  // Webkit's mousewheel event
                          || -e.originalEvent.wheelDelta;  // other's mousewheel event
                if (delta > 0) {
                    if (current_row < 16) {
                        select_row(current_row + 1);
                    }
                }
                else if (delta < 0) {
                    if (current_row > 1) {
                        select_row(current_row - 1);
                    }
                }
                e.preventDefault();
            });
            $(document).on('keydown', function(e) {
                switch (e.originalEvent.keyIdentifier) {
                case 'Down':
                    if (current_row < 16) {
                        select_row(current_row + 1);
                    }
                    break;
                case 'Up':
                    if (current_row > 1) {
                        select_row(current_row - 1);
                    }
                    break;
                }
            });
        }

        if (!opt.noedit) {
            $(".panel").each(function() {
                $(this).on('mousedown', on_panel_mousedown);
                $(this).on('mousemove', on_panel_mousemove);
                $(this).on('mouseup', on_panel_mouseup);
            });
        }
    }

    if (opt['export']) {
        $('#export-button').on('click', function() {
            export_data();
        });
    }
});
<commit_msg>Set 'inline-block' to export button's display property when enabling it.<commit_after>$(function() {
    "use strict";

    var opt = {
        'row': 1,
        'noedit': false,
        'nomask': false,
        'memory': null,
        'export': false,
        'data': null
    };

    var current_row = 0;
    var mouse_tracking;
    var touch_tracking = [];

    // http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
    function is_touch_device() {
        return !!('ontouchstart' in window) // works on most browsers
            || !!('onmsgesturechange' in window); // works on ie10
    }

    function get_data() {
        var data = "";
        for (var i = 1; i <= 16; ++i) {
            for (var j = 1; j <= 16; ++j) {
                var panel = $('#panel' + i + '-' + j);

                if (panel.hasClass('white')) {
                    data += '0';
                }
                else if (panel.hasClass('black')) {
                    data += '1';
                }
                else {
                    data += '.';
                }
            }
        }
        return data;
    }

    function set_data(data) {
        for (var i = 1; i <= 16; ++i) {
            for (var j = 1; j <= 16; ++j) {
                var panel = $('#panel' + i + '-' + j);

                var k = 16 * (i - 1) + (j - 1);
                if (data[k] === '0') {
                    panel.addClass("white invisible-text");
                    panel.removeClass("black");
                }
                else if (data[k] === '1') {
                    panel.addClass("black invisible-text");
                    panel.removeClass("white");
                }
                else {
                    panel.removeClass("white black invisible-text");
                }
            }
        }
    }

    function toggle_panel_color(panel) {
        if ($(panel).hasClass("black") || $(panel).hasClass("white")) {
            $(panel).toggleClass("black white");
        }
        else {
            $(panel).addClass("white invisible-text");
        }
        localStorage['resume_data'] = JSON.stringify({ 'row': current_row, 'data': get_data() });
    }

    function unmagnify_row(i, duration, delay) {
        duration /= 1000;
        delay /= 1000;
        var row = $('#row' + i);
        row.css({
            'transition-duration': '0.001s, ' + duration + 's, ' + duration + 's, ' + duration + 's, ' + duration + 's',
            'transition-delay': delay + 's'
        });
        row.removeClass('magnified');
    }

    function magnify_row(i, duration, delay) {
        duration /= 1000;
        delay /= 1000;
        var row = $('#row' + i);
        row.css({
            'transition-duration': '0.001s, ' + duration + 's, ' + duration + 's, ' + duration + 's, ' + duration + 's',
            'transition-delay': delay + 's'
        });
        row.addClass('magnified');
    }

    function move_focus(i) {
        var row = $('#row' + i);

        var unmasked_top = row.position().top;
        var unmasked_bottom = unmasked_top + row.outerHeight();

        var upper_mask = $('#upper-mask');
        var lower_mask = $('#lower-mask');
        upper_mask.css('transform', 'translate3d(0px, ' + (unmasked_top - upper_mask.outerHeight()) + 'px, 0px)');
        lower_mask.css('transform', 'translate3d(0px, ' + unmasked_bottom + 'px, 0px)');
    }

    function move_cursor_button(i) {
        var row = $('#row' + i);

        var unmasked_top = row.position().top;
        var unmasked_bottom = unmasked_top + row.outerHeight();

        var scaled_top = unmasked_top - 0.3 * row.outerHeight() / 2;
        var scaled_bottom = unmasked_bottom + 0.3 * row.outerHeight() / 2;
        var scaled_left = -0.3 * row.outerWidth() / 2;

        var space = 0;

        var cursor_panel = $('#cursor-panel');
        cursor_panel.css('transform',
            'translate3d(' +
                (scaled_left - 1.3 * 60 / 2 - cursor_panel.outerWidth() / 2) + 'px, ' +
                (unmasked_top + row.outerHeight() / 2 - cursor_panel.outerHeight() / 2 - space) + 'px, ' +
                '0)');
    }

    function initialize_mask_transformations() {
        var board_height = $('#board').outerHeight();

        $('#upper-mask').css('transform', 'translate3d(0px, ' + (-board_height) + 'px, 0px)');
        $('#lower-mask').css('transform', 'translate3d(0px, ' + board_height + 'px, 0px)');
    }

    function select_row(i) {
        unmagnify_row(current_row, 400, 0);
        current_row = i;
        move_focus(current_row);
        move_cursor_button(current_row);
        magnify_row(current_row, 400, 0);
        localStorage['resume_data'] = JSON.stringify({ 'row': current_row, 'data': get_data() });
    }

    function on_panel_mousedown(e) {
        mouse_tracking = { target: this };
        toggle_panel_color(this);
    }

    function on_panel_mousemove(e) {
        if (mouse_tracking && mouse_tracking.target !== this) {
            toggle_panel_color(this);
            mouse_tracking.target = this;
        }
    }

    function on_panel_mouseup(e) {
        mouse_tracking = null;
    }

    function on_panel_touchstart(e) {
        $.each(e.originalEvent.changedTouches, function(_, t) {
            var target = document.elementFromPoint(t.clientX, t.clientY);
            if ($(target).hasClass('panel')) {
                touch_tracking[t.identifier] = { target: target };
                toggle_panel_color(target);
            }
        });
    }

    function on_panel_touchmove(e) {
        $.each(e.originalEvent.changedTouches, function(_, t) {
            var target = document.elementFromPoint(t.clientX, t.clientY);
            if ($(target).hasClass('panel')) {
                if (touch_tracking[t.identifier] && touch_tracking[t.identifier].target !== target) {
                    toggle_panel_color(target);
                    touch_tracking[t.identifier].target = target;
                }
            }
        });
    }

    function on_panel_touchend(e) {
        $.each(e.originalEvent.changedTouches, function(_, t) {
            touch_tracking[t.identifier] = null;
        });
    }

    function change_query_string(qs, kvs) {
        $.each(kvs, function(key, value) {
            var re = new RegExp('([?&])' + key + '=[^&]+');
            if (qs.match(re)) {
                // The key is already used.
                qs = qs.replace(re, '$1' + key + '=' + value);
            }
            else {
                // The key isn't used.
                if (qs.match(/[?&]$/)) {
                    qs += key + '=' + value;
                }
                else if (qs.length > 0) {
                    qs += '&' + key + '=' + value;
                }
                else {
                    qs += '?' + key + '=' + value;
                }
            }
        });
        return qs;
    }

    function export_data() {
        var loc = window.location;
        loc.search = change_query_string(loc.search, { 'data': get_data() });
    }

    function make_thumbnail(data, scale) {
        var canvas = $('<canvas/>');
        canvas[0].width = scale * 16;
        canvas[0].height = scale * 16;
        var ctx = canvas[0].getContext('2d');
        for (var i = 0; i < 16; ++i) {
            for (var j = 0; j < 16; ++j) {
                var k = 16 * i + j;
                if (data[k] === '0') {
                    ctx.fillStyle = 'white';
                }
                else if (data[k] === '1') {
                    ctx.fillStyle = 'black';
                }
                else {
                    ctx.fillStyle = 'gray';
                }
                ctx.fillRect(scale * j, scale * i, scale, scale);
            }
        }
        return canvas[0].toDataURL();
    }

    // process options
    $.each(window.location.search.slice(1).split("&"), function(_, param) {
        var kv = param.split("=");

        switch (kv[0]) {
        case "row":
            opt.row = parseInt(kv[1]);
            break;
        case "noedit":
            opt.noedit = true;
            break;
        case "nomask":
            opt.nomask = true;
            break;
        case "memory":
            switch (kv[1]) {
            case 'save':
                opt.memory = 'save';
                break;
            case 'load':
                opt.memory = 'load';
                break;
            case 'clear':
                opt.memory = 'clear';
                break;
            }
            break;
        case "export":
            opt['export'] = true;
            break;
        case "data":
            opt.data = kv[1];
            break;
        }
    });

    // create the board
    for (var i = 1; i <= 16; ++i) {
        var row = $('

');
        for (var j = 1; j <= 16; ++j) {
            var cell = $('

');
            var panel = $('
?
');
            panel.appendTo(cell);
            cell.appendTo(row);
        }
        row.appendTo('#board');
        var num_str = (i < 10 ? '0' : '') + i;
        $('').appendTo('#board');
    }

    // memory
    switch (opt.memory) {
    case 'load':
        $('#load-panel').css('display', 'block');
        $('.load-button').each(function() {
            var n = parseInt($(this).attr("id").slice('load-button'.length));
            var scale = Math.floor($(this).width() / 16);
            $(this).css('background-image', 'url(' + make_thumbnail(localStorage[n] || '', scale) + ')');
        });
        $('.load-button').on('click', function() {
            var n = parseInt($(this).attr("id").slice('load-button'.length));
            set_data(localStorage[n] || '');
        });
        break;
    case 'save':
        $('#save-panel').css('display', 'block');
        $('.save-button').each(function() {
            var n = parseInt($(this).attr("id").slice('save-button'.length));
            var scale = Math.floor($(this).width() / 16);
            $(this).css('background-image', 'url(' + make_thumbnail(localStorage[n] || '', scale) + ')');
        });
        $('.save-button').on('click', function() {
            var n = parseInt($(this).attr("id").slice('save-button'.length));
            var scale = Math.floor($(this).width() / 16);
            var data = get_data();
            localStorage[n] = data;
            $(this).css('background-image', 'url(' + make_thumbnail(data, scale) + ')');
        });
        break;
    case 'clear':
        localStorage.clear();
        break;
    }

    if (opt.data !== null) {
        set_data(opt.data);
    }
    else if (typeof localStorage['resume_data'] !== 'undefined') {
        set_data(JSON.parse(localStorage['resume_data']).data);
    }

    if (opt['export']) {
        $('#export-button').css('display', 'inline-block');
    }

    if (opt.nomask) {
        $('.mask').css('display', 'none');
        $('#prev-button, #next-button').css('display', 'none');
    }
    else {
        // masks
        initialize_mask_transformations();

        // initial focus
        if (opt.data !== null) {
            current_row = opt.row;
        }
        else if (typeof localStorage['resume_data'] !== 'undefined') {
            current_row = JSON.parse(localStorage['resume_data']).row;
        }
        else {
            current_row = opt.row;
        }
        move_focus(current_row, 400, 0);
        move_cursor_button(current_row, 400, 400);
        magnify_row(current_row, 400, 400);
    }

    // events
    if (is_touch_device()) {
        // disable the overscroll effect on touch-capable environments.
        // http://www.html5rocks.com/en/mobile/touch/
        // http://stackoverflow.com/questions/2890361/disable-scrolling-in-an-iphone-web-application
        $(document).on('touchmove', function(e) {
            e.preventDefault();
        });

        if (!opt.nomask) {
            $('#prev-button').on('touchstart', function() {
                if (current_row > 1) {
                    select_row(current_row - 1);
                }
            });
            $('#next-button').on('touchstart', function() {
                if (current_row < 16) {
                    select_row(current_row + 1);
                }
            });
        }

        if (!opt.noedit) {
            $(".panel").each(function() {
                $(this).on('touchstart', on_panel_touchstart);
                $(this).on('touchmove', on_panel_touchmove);
                $(this).on('touchend', on_panel_touchend);
            });
        }
    }
    else {
        if (!opt.nomask) {
            $('#prev-button').on('click', function() {
                if (current_row > 1) {
                    select_row(current_row - 1);
                }
            });
            $('#next-button').on('click', function() {
                if (current_row < 16) {
                    select_row(current_row + 1);
                }
            });
            $(document).on('wheel mousewheel DOMMouseScroll', function(e) {
                var delta =   e.originalEvent.deltaY       // 'wheel' event
                          || -e.originalEvent.wheelDeltaY  // Webkit's mousewheel event
                          || -e.originalEvent.wheelDelta;  // other's mousewheel event
                if (delta > 0) {
                    if (current_row < 16) {
                        select_row(current_row + 1);
                    }
                }
                else if (delta < 0) {
                    if (current_row > 1) {
                        select_row(current_row - 1);
                    }
                }
                e.preventDefault();
            });
            $(document).on('keydown', function(e) {
                switch (e.originalEvent.keyIdentifier) {
                case 'Down':
                    if (current_row < 16) {
                        select_row(current_row + 1);
                    }
                    break;
                case 'Up':
                    if (current_row > 1) {
                        select_row(current_row - 1);
                    }
                    break;
                }
            });
        }

        if (!opt.noedit) {
            $(".panel").each(function() {
                $(this).on('mousedown', on_panel_mousedown);
                $(this).on('mousemove', on_panel_mousemove);
                $(this).on('mouseup', on_panel_mouseup);
            });
        }
    }

    if (opt['export']) {
        $('#export-button').on('click', function() {
            export_data();
        });
    }
});
<|endoftext|>


<commit_before>enyo.kind({
	name:"enyo.sample.CanvasBallsSample",
	kind:"Control",
	style:"padding:15px;",
	components: [
		{kind:"Canvas", style: "border: 1px solid black;", attributes: {width: 280, height: 300}, components: [
			// a container for the balls
			{name: "ballpit", kind: "canvas.Control"},
			// a visible shelf to bounce off of
			{kind: "canvas.Rectangle", bounds: {l: 0, t: 290, w: 300, h: 10}},
			// an FPS counter
			{name:"fpsCounter", kind: "canvas.Text", bounds: {l: 0, t: 15}, color: "black"}
		]},
		{tag: "br"},
		// Reset the balls / change the number
		{tag: "button", content: "Reset", ontap: "reset"},
		{tag: "br"},
		{tag: "span", content:"Balls: "},
		{kind: "onyx.InputDecorator", components:[
			{kind: "onyx.Input", name: "balls", value: "10", placeholder: "Number of Balls"}
		]}
	],
	published: {
		// force of gravity
		accel: 9.8,
		// number of balls to show
		balls: 10
	},
	setupBalls: function() {
		// pause loop to update the balls
		if (this.cancel) {
			enyo.cancelRequestAnimationFrame(this.cancel);
		}
		this.loopStart = Date.now();
		this.frame = 0;
		this.start = Date.now();
		this.$.ballpit.destroyClientControls();
		var colors = [ "green", "blue", "black", "brown", "red", "orange"];
		var bounce, color, t, l;
		for (var i = 0; i < this.balls; i++) {
			// bounce from 0.30 to 0.99
			bounce = (enyo.irand(69) + 30) / 100;
			color = colors[enyo.irand(colors.length)];
			t = enyo.irand(375);
			l = 10 + (enyo.irand(27) * 10);
			this.$.ballpit.createComponent({
				kind: "canvas.Circle",
				bounds: {l: l, t: t, w: 10},
				color: color,
				bounce: bounce,
				vel: 0,
				owner: this});
		}
		// (re)start loop
		enyo.asyncMethod(this,"loop");
	},
	rendered: function() {
		this.setupBalls();
	},
	destroy: function() {
		if (this.cancel) {
			enyo.cancelRequestAnimationFrame(this.cancel);
		}
		this.inherited(arguments);
	},
	loop: function() {
		this.frame++;
		// update ball positions
		for (var i = 0, b; (b = this.$.ballpit.children[i]); i++) {
			if (b.bounds.t + b.bounds.w > this.$.rectangle.bounds.t) {
				// hits the ground, bounce back with X% of velocity
				b.vel = -b.vel * b.bounce;
				b.bounds.t = this.$.rectangle.bounds.t - b.bounds.w;
			} else if (b.bounds.t < b.bounds.w) {
				// prevent balls from shooting over the ceiling
				b.bounds.t = b.bounds.w;
				b.vel = 0;
			}
			b.vel += this.accel * (Date.now() - this.start);
			// make the distances rather large
			b.bounds.t += (b.vel / 10000);
		}
		this.$.canvas.update();
		this.start = Date.now();
		this.cancel = enyo.requestAnimationFrame(this.bindSafely("loop"));
		// draw the framerate
		this.$.fpsCounter.setText(Math.floor(this.frame / ((Date.now() - this.loopStart) / 1000)));
	},
	reset: function() {
		var inode = this.$.balls.hasNode();
		var newballs = inode ? parseInt(inode.value,0) : this.balls;
		if (isFinite(newballs) && newballs >= 0 && newballs != this.balls) {
			// update the number of balls
			this.setBalls(newballs);
		} else {
			// reset the current balls without destroying / recreating them
			for (var i = 0, b; (b = this.$.ballpit.children[i]); i++) {
				b.bounds.t = enyo.irand(375);
				b.vel = 0;
			}
		}
	},
	ballsChanged: function(inOldBalls) {
		this.setupBalls();
	}
});
<commit_msg>Update CanvasBallsSample.js so it won't crash if it's destroyed before the first loop call can run.<commit_after>enyo.kind({
	name:"enyo.sample.CanvasBallsSample",
	kind:"Control",
	style:"padding:15px;",
	components: [
		{kind:"Canvas", style: "border: 1px solid black;", attributes: {width: 280, height: 300}, components: [
			// a container for the balls
			{name: "ballpit", kind: "canvas.Control"},
			// a visible shelf to bounce off of
			{kind: "canvas.Rectangle", bounds: {l: 0, t: 290, w: 300, h: 10}},
			// an FPS counter
			{name:"fpsCounter", kind: "canvas.Text", bounds: {l: 0, t: 15}, color: "black"}
		]},
		{tag: "br"},
		// Reset the balls / change the number
		{tag: "button", content: "Reset", ontap: "reset"},
		{tag: "br"},
		{tag: "span", content:"Balls: "},
		{kind: "onyx.InputDecorator", components:[
			{kind: "onyx.Input", name: "balls", value: "10", placeholder: "Number of Balls"}
		]}
	],
	published: {
		// force of gravity
		accel: 9.8,
		// number of balls to show
		balls: 10
	},
	setupBalls: function() {
		// pause loop to update the balls
		if (this.cancel) {
			enyo.cancelRequestAnimationFrame(this.cancel);
		}
		this.loopStart = Date.now();
		this.frame = 0;
		this.start = Date.now();
		this.$.ballpit.destroyClientControls();
		var colors = [ "green", "blue", "black", "brown", "red", "orange"];
		var bounce, color, t, l;
		for (var i = 0; i < this.balls; i++) {
			// bounce from 0.30 to 0.99
			bounce = (enyo.irand(69) + 30) / 100;
			color = colors[enyo.irand(colors.length)];
			t = enyo.irand(375);
			l = 10 + (enyo.irand(27) * 10);
			this.$.ballpit.createComponent({
				kind: "canvas.Circle",
				bounds: {l: l, t: t, w: 10},
				color: color,
				bounce: bounce,
				vel: 0,
				owner: this});
		}
		// (re)start loop
		enyo.asyncMethod(this, this.bindSafely(this, "loop"));
	},
	rendered: function() {
		this.setupBalls();
	},
	destroy: function() {
		if (this.cancel) {
			enyo.cancelRequestAnimationFrame(this.cancel);
		}
		this.inherited(arguments);
	},
	loop: function() {
		this.frame++;
		// update ball positions
		for (var i = 0, b; (b = this.$.ballpit.children[i]); i++) {
			if (b.bounds.t + b.bounds.w > this.$.rectangle.bounds.t) {
				// hits the ground, bounce back with X% of velocity
				b.vel = -b.vel * b.bounce;
				b.bounds.t = this.$.rectangle.bounds.t - b.bounds.w;
			} else if (b.bounds.t < b.bounds.w) {
				// prevent balls from shooting over the ceiling
				b.bounds.t = b.bounds.w;
				b.vel = 0;
			}
			b.vel += this.accel * (Date.now() - this.start);
			// make the distances rather large
			b.bounds.t += (b.vel / 10000);
		}
		this.$.canvas.update();
		this.start = Date.now();
		this.cancel = enyo.requestAnimationFrame(this.bindSafely("loop"));
		// draw the framerate
		this.$.fpsCounter.setText(Math.floor(this.frame / ((Date.now() - this.loopStart) / 1000)));
	},
	reset: function() {
		var inode = this.$.balls.hasNode();
		var newballs = inode ? parseInt(inode.value,0) : this.balls;
		if (isFinite(newballs) && newballs >= 0 && newballs != this.balls) {
			// update the number of balls
			this.setBalls(newballs);
		} else {
			// reset the current balls without destroying / recreating them
			for (var i = 0, b; (b = this.$.ballpit.children[i]); i++) {
				b.bounds.t = enyo.irand(375);
				b.vel = 0;
			}
		}
	},
	ballsChanged: function(inOldBalls) {
		this.setupBalls();
	}
});
<|endoftext|>


<commit_before>var fs = require('fs'),
    path = require('path'),
    util = require('util'),
    Stream = require('stream').Stream;

module.exports = flow = function(temporaryFolder) {
    var $ = this;
    $.temporaryFolder = temporaryFolder;
    $.maxFileSize = null;
    $.fileParameterName = 'file';

    try {
        fs.mkdirSync($.temporaryFolder);
    } catch (e) {}

    function cleanIdentifier(identifier) {
        return identifier.replace(/[^0-9A-Za-z_-]/g, '');
    }

    function getChunkFilename(chunkNumber, identifier) {
        // Clean up the identifier
        identifier = cleanIdentifier(identifier);
        // What would the file name be?
        return path.resolve($.temporaryFolder, './flow-' + identifier + '.' + chunkNumber);
    }

    function validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename, fileSize) {
        // Clean up the identifier
        identifier = cleanIdentifier(identifier);

        // Check if the request is sane
        if (chunkNumber == 0 || chunkSize == 0 || totalSize == 0 || identifier.length == 0 || filename.length == 0) {
            return 'non_flow_request';
        }
        var numberOfChunks = Math.max(Math.floor(totalSize / (chunkSize * 1.0)), 1);
        if (chunkNumber > numberOfChunks) {
            return 'invalid_flow_request1';
        }

        // Is the file too big?
        if ($.maxFileSize && totalSize > $.maxFileSize) {
            return 'invalid_flow_request2';
        }

        if (typeof(fileSize) != 'undefined') {
            if (chunkNumber < numberOfChunks && fileSize != chunkSize) {
                // The chunk in the POST request isn't the correct size
                return 'invalid_flow_request3';
            }
            if (numberOfChunks > 1 && chunkNumber == numberOfChunks && fileSize != ((totalSize % chunkSize) + chunkSize)) {
                // The chunks in the POST is the last one, and the fil is not the correct size
                return 'invalid_flow_request4';
            }
            if (numberOfChunks == 1 && fileSize != totalSize) {
                // The file is only a single chunk, and the data size does not fit
                return 'invalid_flow_request5';
            }
        }

        return 'valid';
    }

    //'found', filename, original_filename, identifier
    //'not_found', null, null, null
    $.get = function(req, callback) {
        var chunkNumber = req.param('flowChunkNumber', 0);
        var chunkSize = req.param('flowChunkSize', 0);
        var totalSize = req.param('flowTotalSize', 0);
        var identifier = req.param('flowIdentifier', "");
        var filename = req.param('flowFilename', "");

        if (validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename) == 'valid') {
            var chunkFilename = getChunkFilename(chunkNumber, identifier);
            fs.exists(chunkFilename, function(exists) {
                if (exists) {
                    callback('found', chunkFilename, filename, identifier);
                } else {
                    callback('not_found', null, null, null);
                }
            });
        } else {
            callback('not_found', null, null, null);
        }
    };

    //'partly_done', filename, original_filename, identifier
    //'done', filename, original_filename, identifier
    //'invalid_flow_request', null, null, null
    //'non_flow_request', null, null, null
    $.post = function(req, callback) {

        var fields = req.body;
        var files = req.files;

        var chunkNumber = fields['flowChunkNumber'];
        var chunkSize = fields['flowChunkSize'];
        var totalSize = fields['flowTotalSize'];
        var identifier = cleanIdentifier(fields['flowIdentifier']);
        var filename = fields['flowFilename'];

        var original_filename = fields['flowIdentifier'];

        if (!files[$.fileParameterName] || !files[$.fileParameterName].size) {
            callback('invalid_flow_request', null, null, null);
            return;
        }
        var validation = validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename, files[$.fileParameterName].size);
        if (validation == 'valid') {
            var chunkFilename = getChunkFilename(chunkNumber, identifier);

            // Save the chunk (TODO: OVERWRITE)
            fs.rename(files[$.fileParameterName].path, chunkFilename, function() {

                // Do we have all the chunks?
                var currentTestChunk = 1;
                var numberOfChunks = Math.max(Math.floor(totalSize / (chunkSize * 1.0)), 1);
                var testChunkExists = function() {
                    fs.exists(getChunkFilename(currentTestChunk, identifier), function(exists) {
                        if (exists) {
                            currentTestChunk++;
                            if (currentTestChunk > numberOfChunks) {
                                callback('done', filename, original_filename, identifier);
                            } else {
                                // Recursion
                                testChunkExists();
                            }
                        } else {
                            callback('partly_done', filename, original_filename, identifier);
                        }
                    });
                };
                testChunkExists();
            });
        } else {
            callback(validation, filename, original_filename, identifier);
        }
    };

    // Pipe chunks directly in to an existsing WritableStream
    //   r.write(identifier, response);
    //   r.write(identifier, response, {end:false});
    //
    //   var stream = fs.createWriteStream(filename);
    //   r.write(identifier, stream);
    //   stream.on('data', function(data){...});
    //   stream.on('finish', function(){...});
    $.write = function(identifier, writableStream, options) {
        options = options || {};
        options.end = (typeof options['end'] == 'undefined' ? true : options['end']);

        // Iterate over each chunk
        var pipeChunk = function(number) {

            var chunkFilename = getChunkFilename(number, identifier);
            fs.exists(chunkFilename, function(exists) {

                if (exists) {
                    // If the chunk with the current number exists,
                    // then create a ReadStream from the file
                    // and pipe it to the specified writableStream.
                    var sourceStream = fs.createReadStream(chunkFilename);
                    sourceStream.pipe(writableStream, {
                        end: false
                    });
                    sourceStream.on('end', function() {
                        // When the chunk is fully streamed,
                        // jump to the next one
                        pipeChunk(number + 1);
                    });
                } else {
                    // When all the chunks have been piped, end the stream
                    if (options.end) writableStream.end();
                    if (options.onDone) options.onDone();
                }
            });
        };
        pipeChunk(1);
    };

    $.clean = function(identifier, options) {
        options = options || {};

        // Iterate over each chunk
        var pipeChunkRm = function(number) {

            var chunkFilename = getChunkFilename(number, identifier);

            //console.log('removing pipeChunkRm ', number, 'chunkFilename', chunkFilename);
            fs.exists(chunkFilename, function(exists) {
                if (exists) {

                    console.log('exist removing ', chunkFilename);
                    fs.unlink(chunkFilename, function(err) {
                        if (options.onError) options.onError(err);
                    });

                    pipeChunkRm(number + 1);

                } else {

                    if (options.onDone) options.onDone();

                }
            });
        };
        pipeChunkRm(1);
    };

    return $;
};
<commit_msg>Fixed an error in validateRequest<commit_after>var fs = require('fs'),
    path = require('path'),
    util = require('util'),
    Stream = require('stream').Stream;

module.exports = flow = function(temporaryFolder) {
    var $ = this;
    $.temporaryFolder = temporaryFolder;
    $.maxFileSize = null;
    $.fileParameterName = 'file';

    try {
        fs.mkdirSync($.temporaryFolder);
    } catch (e) {}

    function cleanIdentifier(identifier) {
        return identifier.replace(/[^0-9A-Za-z_-]/g, '');
    }

    function getChunkFilename(chunkNumber, identifier) {
        // Clean up the identifier
        identifier = cleanIdentifier(identifier);
        // What would the file name be?
        return path.resolve($.temporaryFolder, './flow-' + identifier + '.' + chunkNumber);
    }

    function validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename, fileSize) {
        // Clean up the identifier
        identifier = cleanIdentifier(identifier);

        // Check if the request is sane
        if (chunkNumber == 0 || chunkSize == 0 || totalSize == 0 || identifier.length == 0 || filename.length == 0) {
            return 'non_flow_request';
        }
        var numberOfChunks = Math.max(Math.floor(totalSize / (chunkSize * 1.0)), 1);
        if (chunkNumber > numberOfChunks) {
            return 'invalid_flow_request1';
        }

        // Is the file too big?
        if ($.maxFileSize && totalSize > $.maxFileSize) {
            return 'invalid_flow_request2';
        }

        if (typeof(fileSize) != 'undefined') {
            if (chunkNumber < numberOfChunks && fileSize != chunkSize) {
                // The chunk in the POST request isn't the correct size
                return 'invalid_flow_request3';
            }
            if (numberOfChunks > 1 && chunkNumber == numberOfChunks && fileSize != ((totalSize % chunkSize) + parseInt(chunkSize))) {
                // The chunks in the POST is the last one, and the fil is not the correct size
                return 'invalid_flow_request4';
            }
            if (numberOfChunks == 1 && fileSize != totalSize) {
                // The file is only a single chunk, and the data size does not fit
                return 'invalid_flow_request5';
            }
        }

        return 'valid';
    }

    //'found', filename, original_filename, identifier
    //'not_found', null, null, null
    $.get = function(req, callback) {
        var chunkNumber = req.param('flowChunkNumber', 0);
        var chunkSize = req.param('flowChunkSize', 0);
        var totalSize = req.param('flowTotalSize', 0);
        var identifier = req.param('flowIdentifier', "");
        var filename = req.param('flowFilename', "");

        if (validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename) == 'valid') {
            var chunkFilename = getChunkFilename(chunkNumber, identifier);
            fs.exists(chunkFilename, function(exists) {
                if (exists) {
                    callback('found', chunkFilename, filename, identifier);
                } else {
                    callback('not_found', null, null, null);
                }
            });
        } else {
            callback('not_found', null, null, null);
        }
    };

    //'partly_done', filename, original_filename, identifier
    //'done', filename, original_filename, identifier
    //'invalid_flow_request', null, null, null
    //'non_flow_request', null, null, null
    $.post = function(req, callback) {

        var fields = req.body;
        var files = req.files;

        var chunkNumber = fields['flowChunkNumber'];
        var chunkSize = fields['flowChunkSize'];
        var totalSize = fields['flowTotalSize'];
        var identifier = cleanIdentifier(fields['flowIdentifier']);
        var filename = fields['flowFilename'];

        var original_filename = fields['flowIdentifier'];

        if (!files[$.fileParameterName] || !files[$.fileParameterName].size) {
            callback('invalid_flow_request', null, null, null);
            return;
        }
        var validation = validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename, files[$.fileParameterName].size);
        if (validation == 'valid') {
            var chunkFilename = getChunkFilename(chunkNumber, identifier);

            // Save the chunk (TODO: OVERWRITE)
            fs.rename(files[$.fileParameterName].path, chunkFilename, function() {

                // Do we have all the chunks?
                var currentTestChunk = 1;
                var numberOfChunks = Math.max(Math.floor(totalSize / (chunkSize * 1.0)), 1);
                var testChunkExists = function() {
                    fs.exists(getChunkFilename(currentTestChunk, identifier), function(exists) {
                        if (exists) {
                            currentTestChunk++;
                            if (currentTestChunk > numberOfChunks) {
                                callback('done', filename, original_filename, identifier);
                            } else {
                                // Recursion
                                testChunkExists();
                            }
                        } else {
                            callback('partly_done', filename, original_filename, identifier);
                        }
                    });
                };
                testChunkExists();
            });
        } else {
            callback(validation, filename, original_filename, identifier);
        }
    };

    // Pipe chunks directly in to an existsing WritableStream
    //   r.write(identifier, response);
    //   r.write(identifier, response, {end:false});
    //
    //   var stream = fs.createWriteStream(filename);
    //   r.write(identifier, stream);
    //   stream.on('data', function(data){...});
    //   stream.on('finish', function(){...});
    $.write = function(identifier, writableStream, options) {
        options = options || {};
        options.end = (typeof options['end'] == 'undefined' ? true : options['end']);

        // Iterate over each chunk
        var pipeChunk = function(number) {

            var chunkFilename = getChunkFilename(number, identifier);
            fs.exists(chunkFilename, function(exists) {

                if (exists) {
                    // If the chunk with the current number exists,
                    // then create a ReadStream from the file
                    // and pipe it to the specified writableStream.
                    var sourceStream = fs.createReadStream(chunkFilename);
                    sourceStream.pipe(writableStream, {
                        end: false
                    });
                    sourceStream.on('end', function() {
                        // When the chunk is fully streamed,
                        // jump to the next one
                        pipeChunk(number + 1);
                    });
                } else {
                    // When all the chunks have been piped, end the stream
                    if (options.end) writableStream.end();
                    if (options.onDone) options.onDone();
                }
            });
        };
        pipeChunk(1);
    };

    $.clean = function(identifier, options) {
        options = options || {};

        // Iterate over each chunk
        var pipeChunkRm = function(number) {

            var chunkFilename = getChunkFilename(number, identifier);

            //console.log('removing pipeChunkRm ', number, 'chunkFilename', chunkFilename);
            fs.exists(chunkFilename, function(exists) {
                if (exists) {

                    console.log('exist removing ', chunkFilename);
                    fs.unlink(chunkFilename, function(err) {
                        if (options.onError) options.onError(err);
                    });

                    pipeChunkRm(number + 1);

                } else {

                    if (options.onDone) options.onDone();

                }
            });
        };
        pipeChunkRm(1);
    };

    return $;
};
<|endoftext|>


<commit_before>import * as React from "react";
import { render } from "react-dom";
import { Chart } from "react-google-charts";

const data = [
  ["Country", "Popularity"],
  ["Germany", 200],
  ["United States", 300],
  ["Brazil", 400],
  ["Canada", 500],
  ["France", 600],
  ["RU", 700]
];
class App extends React.Component {
  render() {
    return (

        <Chart
          chartEvents={[
            {
              eventName: "select",
              callback: ({ chartWrapper }) => {
                const chart = chartWrapper.getChart();
                const selection = chart.getSelection();
                if (selection.length === 0) return;
                const region = data[selection[0].row];
                console.log("Selected : " + region);
              }
            }
          ]}
          chartType="GeoChart"
          width="100%"
          height="400px"
          data={data}
        />

    );
  }
}

render(<App />, document.getElementById("root"));
<commit_msg>docs: [geochart] off by one error.<commit_after>import * as React from "react";
import { render } from "react-dom";
import { Chart } from "react-google-charts";

const data = [
  ["Country", "Popularity"],
  ["Germany", 200],
  ["United States", 300],
  ["Brazil", 400],
  ["Canada", 500],
  ["France", 600],
  ["RU", 700]
];
class App extends React.Component {
  render() {
    return (

        <Chart
          chartEvents={[
            {
              eventName: "select",
              callback: ({ chartWrapper }) => {
                const chart = chartWrapper.getChart();
                const selection = chart.getSelection();
                if (selection.length === 0) return;
                const region = data[selection[0].row + 1];
                console.log("Selected : " + region);
              }
            }
          ]}
          chartType="GeoChart"
          width="100%"
          height="400px"
          data={data}
        />

    );
  }
}

render(<App />, document.getElementById("root"));
<|endoftext|>


<commit_before>/*
 Licensed to the Apache Software Foundation (ASF) under one or more
 contributor license agreements.  See the NOTICE file distributed with
 this work for additional information regarding copyright ownership.
 The ASF licenses this file to You under the Apache License, Version 2.0
 (the "License"); you may not use this file except in compliance with
 the License.  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/
solrAdminApp.controller('ClusterSuggestionsController',
function($scope,$http) {
    $scope.data={}
    var dataArr =[];
    var dataJson = {};
    //function to display suggestion
    $http({
       method: 'GET',
       url: '/api/cluster/autoscaling/suggestions'
    }).then(function successCallback(response) {
         $scope.data = response.data;
         $scope.parsedData = $scope.data.suggestions;
      }, function errorCallback(response) {
      });
    //function to perform operation
    $scope.postdata = function (x) {
        x.loading = true;
        var path=x.operation.path;
        var command=x.operation.command;
        var fullPath='/api/'+path;
        console.log(fullPath);
        console.log(command);
        $http.post(fullPath, JSON.stringify(command)).then(function (response) {
            if (response.data)
            console.log(response.data);
            x.loading = false;
            x.done = true;
            x.run=true;
            $scope.msg = "Post Data Submitted Successfully!";
        }, function (response) {
            x.failed=true;
            $scope.msg = "Service not Exists";
            $scope.statusval = response.status;
            $scope.statustext = response.statusText;
            $scope.headers = response.headers();
        });
    };
    $scope.showPopover = function() {
      $scope.popup = true;
    };

    $scope.hidePopover = function () {
      $scope.popup = false;
    };
});
<commit_msg>SOLR-11648: Ensure cluster-suggestions page resets menu so that collection and core list are displayed on page load<commit_after>/*
 Licensed to the Apache Software Foundation (ASF) under one or more
 contributor license agreements.  See the NOTICE file distributed with
 this work for additional information regarding copyright ownership.
 The ASF licenses this file to You under the Apache License, Version 2.0
 (the "License"); you may not use this file except in compliance with
 the License.  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/
solrAdminApp.controller('ClusterSuggestionsController',
function($scope, $http, Constants) {
    $scope.resetMenu("cluster-suggestions", Constants.IS_ROOT_PAGE);
    $scope.data={};
    var dataArr =[];
    var dataJson = {};
    //function to display suggestion
    $http({
       method: 'GET',
       url: '/api/cluster/autoscaling/suggestions'
    }).then(function successCallback(response) {
         $scope.data = response.data;
         $scope.parsedData = $scope.data.suggestions;
      }, function errorCallback(response) {
      });
    //function to perform operation
    $scope.postdata = function (x) {
        x.loading = true;
        var path=x.operation.path;
        var command=x.operation.command;
        var fullPath='/api/'+path;
        console.log(fullPath);
        console.log(command);
        $http.post(fullPath, JSON.stringify(command)).then(function (response) {
            if (response.data)
            console.log(response.data);
            x.loading = false;
            x.done = true;
            x.run=true;
            $scope.msg = "Post Data Submitted Successfully!";
        }, function (response) {
            x.failed=true;
            $scope.msg = "Service not Exists";
            $scope.statusval = response.status;
            $scope.statustext = response.statusText;
            $scope.headers = response.headers();
        });
    };
    $scope.showPopover = function() {
      $scope.popup = true;
    };

    $scope.hidePopover = function () {
      $scope.popup = false;
    };
});
<|endoftext|>