<commit_before>module.exports = {
    csgo: {
        type: 'author_flair_text',
        list: [
            '/r/globaloffensive janitor',
            '/r/globaloffensive moderator',
            '/r/globaloffensive monsorator',
            '3dmax fan',
            '400k hype',
            '5 year subreddit veteran',
            'astana dragons fan',
            'astralis fan',
            'baggage veteran',
            'banner competition #1 winner',
            'banner competition #2 first place winner',
            'banner competition #2 second place winner',
            'banner competition #2 third place winner',
            'bloodhound',
            'bravado gaming fan',
            'bravo',
            'cache veteran',
            'cheeky moderator',
            'chroma',
            'clan-mystik fan',
            'cloud9 fan',
            'cloud9 g2a fan',
            'cobblestone veteran',
            'complexity fan',
            'copenhagen wolves fan',
            'counter logic gaming fan',
            'dat team fan',
            'dust 2 veteran',
            'envyus fan',
            'epsilon esports fan',
            'esc gaming fan',
            'extra life tournament caster',
            'extra life tournament finalist',
            'faze clan fan',
            'flipsid3 tactics fan',
            'flipside tactics fan',
            'fnatic fan',
            'fnatic fanatic',
            'g2 esports fan',
            'gambit gaming fan',
            'gamers2 fan',
            'godsent fan',
            'guardian 2',
            'guardian elite',
            'guardian',
            'hellraisers fan',
            'ibuypower fan',
            'inferno veteran',
            'italy veteran',
            'keyd stars fan',
            'kinguin fan',
            'ldlc fan',
            'legendary chicken master',
            'legendary lobster master',
            'lgb esports fan',
            'london consipracy fan',
            'london conspiracy fan',
            'luminosity gaming fan',
            'militia veteran',
            'mirage veteran',
            'moderator',
            'mousesports fan',
            'myxmg fan',
            'n!faculty fan',
            'natus vincere fan',
            'ninjas in pyjamas fan',
            'north fan',
            'nuke vetera',
            'nuke veteran',
            'office veteran',
            'official dunkmaster',
            'one bot to rule them all',
            'optic gaming fan',
            'overpass veteran',
            'penta esports fan',
            'penta sports fan',
            'phoenix',
            'planetkey dynamics fan',
            'reason gaming fan',
            'recursive fan',
            'renegades fan',
            'sk gaming fan',
            'splyce fan',
            'tactics',
            'team astralis fan',
            'team dignitas fan',
            'team ebettle fan',
            'team envyus fan',
            'team immunity fan',
            'team kinguin fan',
            'team liquid fan',
            'team solomid fan',
            'team wolf fan',
            'titan fan',
            'train veteran',
            'trial moderator',
            'tsm kinguin fan',
            'universal soldiers fan',
            'valeria',
            'verygames fan',
            'vexed gaming fan',
            'victory',
            'virtus pro fan',
            'virtus.pro fan',
            'vox eminor fan',
            'xapso fan',
        ]
    },
    rainbow6: {
        type: 'author_flair_css_class',
        list: [
            'ash',
            'ashnew',
            'bandit',
            'banditnew',
            'blackbeard',
            'blackbeardnew',
            'blitz',
            'blitznew',
            'buck',
            'bucknew',
            'capitao',
            'capitaonew',
            'castle',
            'castlenew',
            'caveira',
            'caveiranew',
            'doc',
            'docnew',
            'frost',
            'frostnew',
            'fuze',
            'fuzenew',
            'glaz',
            'glaznew',
            'iq',
            'iqnew',
            'jager',
            'jagernew',
            'kapkan',
            'kapkannew',
            'mod',
            'montagne',
            'montagnenew',
            'mute',
            'mutenew',
            'pulse',
            'pulsenew',
            'recruit',
            'rook',
            'rooknew',
            'sledge',
            'sledgenew',
            'smoke',
            'smokenew',
            'tachanka',
            'tachankanew',
            'thatcher',
            'thatchernew',
            'thermite',
            'thermitenew',
            'twitch',
            'twitchnew',
            'valkyrie',
            'valkyrienew',
        ]
    },
    battlefield1: {
        type: 'author_flair_css_class',
        list: [
            'xbox',
            'origin',
            'psn',
        ]
    },
    ark: {
        type: 'author_flair_css_class',
        list: [
            'arkstone',
            'dodo-blue',
            'dodo-cyan',
            'dodo-default',
            'dodo-lime',
            'dodo-red',
            'dodo-yellow',
            'mushroom',
            'woodsign',
        ]
    },
    rimworld: {
        type: 'author_flair_css_class',
        list: [
            'fun',
            'gold',
            'granite',
            'jade',
            'limestone',
            'marble',
            'mod',
            'plasteel',
            'sandstone',
            'mod2',
            'silver',
            'slate',
            'steel',
            'uranium',
            'war',
            'wood',
        ]
    },
    elite: {
        type: 'author_flair_css_class',
        list: [
            'bot img',
            'cmdr img alliance',
            'cmdr img cobra',
            'cmdr img empire',
            'cmdr img federation',
            'cmdr img sidey',
            'cmdr img skull',
            'cmdr img viper',
            'cmdr',
            'img viper cmdr',
            'mostlyharmless cmdr',
            'star',
        ]
    },
};
<commit_msg>Update flairs for Elite<commit_after>module.exports = {
    csgo: {
        type: 'author_flair_text',
        list: [
            '/r/globaloffensive janitor',
            '/r/globaloffensive moderator',
            '/r/globaloffensive monsorator',
            '3dmax fan',
            '400k hype',
            '5 year subreddit veteran',
            'astana dragons fan',
            'astralis fan',
            'baggage veteran',
            'banner competition #1 winner',
            'banner competition #2 first place winner',
            'banner competition #2 second place winner',
            'banner competition #2 third place winner',
            'bloodhound',
            'bravado gaming fan',
            'bravo',
            'cache veteran',
            'cheeky moderator',
            'chroma',
            'clan-mystik fan',
            'cloud9 fan',
            'cloud9 g2a fan',
            'cobblestone veteran',
            'complexity fan',
            'copenhagen wolves fan',
            'counter logic gaming fan',
            'dat team fan',
            'dust 2 veteran',
            'envyus fan',
            'epsilon esports fan',
            'esc gaming fan',
            'extra life tournament caster',
            'extra life tournament finalist',
            'faze clan fan',
            'flipsid3 tactics fan',
            'flipside tactics fan',
            'fnatic fan',
            'fnatic fanatic',
            'g2 esports fan',
            'gambit gaming fan',
            'gamers2 fan',
            'godsent fan',
            'guardian 2',
            'guardian elite',
            'guardian',
            'hellraisers fan',
            'ibuypower fan',
            'inferno veteran',
            'italy veteran',
            'keyd stars fan',
            'kinguin fan',
            'ldlc fan',
            'legendary chicken master',
            'legendary lobster master',
            'lgb esports fan',
            'london consipracy fan',
            'london conspiracy fan',
            'luminosity gaming fan',
            'militia veteran',
            'mirage veteran',
            'moderator',
            'mousesports fan',
            'myxmg fan',
            'n!faculty fan',
            'natus vincere fan',
            'ninjas in pyjamas fan',
            'north fan',
            'nuke vetera',
            'nuke veteran',
            'office veteran',
            'official dunkmaster',
            'one bot to rule them all',
            'optic gaming fan',
            'overpass veteran',
            'penta esports fan',
            'penta sports fan',
            'phoenix',
            'planetkey dynamics fan',
            'reason gaming fan',
            'recursive fan',
            'renegades fan',
            'sk gaming fan',
            'splyce fan',
            'tactics',
            'team astralis fan',
            'team dignitas fan',
            'team ebettle fan',
            'team envyus fan',
            'team immunity fan',
            'team kinguin fan',
            'team liquid fan',
            'team solomid fan',
            'team wolf fan',
            'titan fan',
            'train veteran',
            'trial moderator',
            'tsm kinguin fan',
            'universal soldiers fan',
            'valeria',
            'verygames fan',
            'vexed gaming fan',
            'victory',
            'virtus pro fan',
            'virtus.pro fan',
            'vox eminor fan',
            'xapso fan',
        ]
    },
    rainbow6: {
        type: 'author_flair_css_class',
        list: [
            'ash',
            'ashnew',
            'bandit',
            'banditnew',
            'blackbeard',
            'blackbeardnew',
            'blitz',
            'blitznew',
            'buck',
            'bucknew',
            'capitao',
            'capitaonew',
            'castle',
            'castlenew',
            'caveira',
            'caveiranew',
            'doc',
            'docnew',
            'frost',
            'frostnew',
            'fuze',
            'fuzenew',
            'glaz',
            'glaznew',
            'iq',
            'iqnew',
            'jager',
            'jagernew',
            'kapkan',
            'kapkannew',
            'mod',
            'montagne',
            'montagnenew',
            'mute',
            'mutenew',
            'pulse',
            'pulsenew',
            'recruit',
            'rook',
            'rooknew',
            'sledge',
            'sledgenew',
            'smoke',
            'smokenew',
            'tachanka',
            'tachankanew',
            'thatcher',
            'thatchernew',
            'thermite',
            'thermitenew',
            'twitch',
            'twitchnew',
            'valkyrie',
            'valkyrienew',
        ]
    },
    battlefield1: {
        type: 'author_flair_css_class',
        list: [
            'xbox',
            'origin',
            'psn',
        ]
    },
    ark: {
        type: 'author_flair_css_class',
        list: [
            'arkstone',
            'dodo-blue',
            'dodo-cyan',
            'dodo-default',
            'dodo-lime',
            'dodo-red',
            'dodo-yellow',
            'mushroom',
            'woodsign',
        ]
    },
    rimworld: {
        type: 'author_flair_css_class',
        list: [
            'fun',
            'gold',
            'granite',
            'jade',
            'limestone',
            'marble',
            'mod',
            'plasteel',
            'sandstone',
            'mod2',
            'silver',
            'slate',
            'steel',
            'uranium',
            'war',
            'wood',
        ]
    },
    elite: {
        type: 'author_flair_css_class',
        list: [
            'bot img',
            'cmdr img alliance',
            'cmdr img cobra',
            'cmdr img empire',
            'cmdr img federation',
            'cmdr img sidey',
            'cmdr img skull',
            'cmdr img viper',
            'cmdr',
            'harmless cmdr',
            'img viper cmdr',
            'mostlyharmless cmdr',
            'star',
        ]
    },
};
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2011-2012 by Nikhil Marathe <nsm.nikhil@gmail.com>
 * 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 node_constants = require('constants');
var async = require('async');
var constants = require('./constants');
var util = require('./util');
var rpc = require('./rpc');
var Bucket = require('./bucket').Bucket;
var _ = require('underscore');
_.str = require('underscore.string');

// abbreviate message utilities
var MC = util.message_contact;
var MID = util.message_rpcID;

exports.KNode = function(desc) {
    // TODO: probably want to persist nodeID
    this.self = _.defaults({ nodeID: util.nodeID(desc.address, desc.port) }, desc);
    Object.freeze(this.self);

    this._storage = {};
    // object treated as an array
    this._buckets = {};
    this._rpc = new rpc.RPC(this.self, _.bind(this._onMessage, this));

    // DEBUG
    setInterval(_.bind(this.debug, this), 5000);
}

exports.KNode.prototype._MSG = function(type, params) {
    // NOTE: always keep this.self last. This way users of _MSG
    // don't have to worry about accidentally overriding self properties
    return _.extend({ type: type}, params, this.self);
}

exports.KNode.prototype._onMessage = function(message) {
    var methodName = '_on' + _.str.titleize(_.str.camelize(message.type.toLowerCase()));
    var action = this[methodName];
    if (action) {
        this._updateContact(MC(message));
        _.bind(action, this)(message);
    }
    else
        console.warn("Unknown message", message);
}

exports.KNode.prototype._ping = function(contact) {

    function onPong(err, message) {
        if (err && err.errno == node_constants.ETIMEDOUT) {
            // TODO: handle peer death
        }
        else {
            assert.equal(message.type, 'PONG');
            this._updateContact(MC(message));
        }
    }

    this._rpc.send(contact, this._MSG('PING'), _.bind(onPong, this));
}

exports.KNode.prototype._onPing = function(message) {
    // this can be made more intelligent such that
    // if an outgoing message is present, piggyback the pong
    // onto it rather than sending it separately
    this._rpc.send(MC(message), this._MSG('PONG', {'replyTo': MID(message)}));
}

exports.KNode.prototype._updateContact = function(contact, cb) {
    var callback = cb || function() {};
    var bucketIndex = util.bucketIndex(this.self.nodeID, contact.nodeID);
    assert.ok(bucketIndex < constants.B);
    if (!this._buckets[bucketIndex])
        this._buckets[bucketIndex] = new Bucket();

    var bucket = this._buckets[bucketIndex];
    contact.lastSeen = Date.now();

    var exists = bucket.contains(contact);
    if (exists) {
        // move to the end of the bucket
        bucket.remove(contact);
        bucket.add(contact);
        callback();
    }
    else if (bucket.size() < constants.K) {
        bucket.add(contact);
        callback();
    }
    else {
        this._rpc.send(bucket.get(0), this._MSG('PING'),
            _.bind(function(err, message) {
                if( err ) {
                    // add new contact, old one is dead
                    bucket.removeIndex(0);
                    bucket.add(contact);
                }
                else {
                }
                callback();
            }, this)
        );
    }
}

// TODO: handle large values which
// won't fit in single UDP packets
exports.KNode.prototype._onStore = function(message) {
    assert.equal(message.key.length, constants.B/4);
    this._storage[message.key] = _.clone(message.value);
    this._rpc.send(MC(message), this._MSG('STORE_REPLY', {
        'replyTo': MID(message),
        'status': true
    }));
}

// This is just to prevent Unknown message errors
exports.KNode.prototype._onStoreReply = function() {}

// debug methods FIXME: remove
exports.KNode.prototype._onGet = function(message) {
    this.get(message.key, function(err, val) {
        console.log('ONGET', val);
    });
}

exports.KNode.prototype._onFindValue = function(message) {
    assert.ok(message.key);
    assert.equal(message.key.length, constants.B/4);
    if (this._storage.hasOwnProperty(message.key)) {
        this._rpc.send(MC(message), this._MSG('FIND_VALUE_REPLY', {
            'replyTo': MID(message),
            'found': true,
            'value': this._storage[message.key]
        }));
    }
    else {
        var contacts = this._findClosestNodes(message.key, constants.K, MC(message).nodeID);

        this._rpc.send(MC(message), this._MSG('FIND_NODE_REPLY', {
            'replyTo': MID(message),
            'contacts': contacts
        }));
    }
}

exports.KNode.prototype._findClosestNodes = function(key, howMany, exclude) {
    var contacts = [];
    function addContact(contact) {
        if (contacts.length >= howMany)
            return;

        if (contact.nodeID == exclude)
            return;

        contacts.push(contact);
    }

    function addClosestFromBucket(bucket) {
        var distances = _.map(bucket.contacts(), function(contact) {
            return {
                distance: util.distance(contact.nodeID, key),
                contact: contact
            };
        });

        distances.sort(function(a, b) {
            return util.buffer_compare(a.distance, b.distance);
        });

        _(distances).chain()
         .first(howMany - contacts.length)
         .pluck('contact')
         .map(MC)
         .value()
         .forEach(addContact);
    }

    // first check the same bucket
    // what bucket would key go into, that is the closest
    // bucket we start from, hence bucketIndex
    // is with reference to self.nodeID
    var bucketIndex = util.bucketIndex(this.self.nodeID, key);
    if (this._buckets.hasOwnProperty(bucketIndex))
        addClosestFromBucket(this._buckets[bucketIndex]);

    var oldBucketIndex = bucketIndex;
    // then check buckets higher up
    while (contacts.length < howMany && bucketIndex < constants.B) {
        bucketIndex++;
        if (this._buckets.hasOwnProperty(bucketIndex))
            addClosestFromBucket(this._buckets[bucketIndex]);
    }

    // then check buckets lower down
    // since Kademlia does not define the search strategy, we can cheat
    // and use this strategy although it may not actually return the closest
    // FIXME: be intelligent to choose actual closest
    bucketIndex = oldBucketIndex;
    while (contacts.length < howMany && bucketIndex >= 0) {
        bucketIndex--;
        if (this._buckets.hasOwnProperty(bucketIndex))
            addClosestFromBucket(this._buckets[bucketIndex]);
    }
    return contacts;
}

exports.KNode.prototype._refreshBucket = function(bucketIndex) {
    var random = util.randomInBucketRangeBuffer(bucketIndex);
    this._iterativeFindNode(random.toString('hex'));
}

// this is a primitive operation, no network activity allowed
exports.KNode.prototype._onFindNode = function(message) {
    assert.ok(message.key);

    var contacts = this._findClosestNodes(message.key, constants.K, MC(message).nodeID);

    this._rpc.send(MC(message), this._MSG('FIND_NODE_REPLY', {
        'replyTo': MID(message),
        'contacts': contacts
    }));
}

// cb should be function(err, type, result)
// where type == 'VALUE' -> result is the value
//       type == 'NODE'  -> result is [list of contacts]
exports.KNode.prototype._iterativeFind = function(key, mode, cb) {
    assert.ok(_.include(['NODE', 'VALUE'], mode));
    var externalCallback = cb || function() {};

    var closestNode = null, previousClosestNode = null;
    var closestNodeDistance = -1;
    var shortlist = this._findClosestNodes(key, constants.ALPHA, this.self.nodeID);
    var contacted = {};
    var foundValue = false;
    var value = null;
    var contactsWhichHadValue = [];
    closestNode = shortlist[0];
    if (!closestNode) {
        // we aren't connected to the overlay network!
        externalCallback({ message: 'Not connected to overlay network. No peers.'}, mode, null);
        return;
    }
    closestNodeDistance = util.distance(key, closestNode.nodeID);

    function xyz(alphaContacts) {
        // clone because we're going to be modifying inside
        async.forEach(alphaContacts, _.bind(function(contact, callback) {
            this._rpc.send(contact, this._MSG('FIND_'+mode, {
                key: key
            }), _.bind(function(err, message) {
                if (err) {
                    console.log("ERROR in iterativeFind"+_.str.titleize(mode)+" send to", contact);
                    shortlist = _.reject(shortlist, function(el) { return el.nodeID == contact.nodeID; });
                }
                else {
                    this._updateContact(contact);
                    contacted[contact.nodeID] = true;
                    var dist = util.distance(key, contact.nodeID);
                    if (util.buffer_compare(dist, closestNodeDistance) == -1) {
                        previousClosestNode = closestNode;
                        closestNode = contact;
                        closestNodeDistance = dist;
                    }

                    if (message.found && mode == 'VALUE') {
                        // TODO: iterative find value when it does find the
                        // value should store the value at the closest node which
                        // DID NOT have the value
                        foundValue = true;
                        value = message.value;
                        contactsWhichHadValue.push(contact.nodeID);
                    }
                    else {
                        shortlist = shortlist.concat(message.contacts);
                        shortlist = _.uniq(shortlist, false /* is sorted? */, function(contact) {
                            return contact.nodeID;
                        });
                    }
                }
                callback();
            }, this));
        }, this), _.bind(function(err) {
            if (foundValue) {
                console.log("Found value from ", contactsWhichHadValue);
                // closest node without value

                var thisNodeID = this.self.nodeID;
                var contactsWithoutValue = _.reject(shortlist, function(contact) {
                    return _.include(contactsWhichHadValue, contact.nodeID);
                });

                var distances = _.map(contactsWithoutValue, function(contact) {
                    return {
                        distance: util.distance(contact.nodeID, thisNodeID),
                        contact: contact
                    };
                });

                distances.sort(function(a, b) {
                    return util.buffer_compare(a.distance, b.distance);
                });

                if (distances.length >= 1) {
                    var closestWithoutValue = distances[0].contact;

                    console.log("Closest is ", closestWithoutValue);
                    var message = this._MSG('STORE', {
                        'key': key,
                        'value': value
                    });
                    this._rpc.send(closestWithoutValue, message);
                }
                externalCallback(null, 'VALUE', value);
                return;
            }

            if (closestNode == previousClosestNode || shortlist.length >= constants.K) {
                // TODO: clarify we might have to do a FIND_NODE here too
                externalCallback(null, 'NODE', shortlist);
                return;
            }

            var remain = _.reject(shortlist, function(el) { return contacted[el.nodeID]; })
            // TODO: call only on alpha random
            if (remain.length == 0)
                externalCallback(null, 'NODE', shortlist);
            else
                _.bind(xyz, this)(remain);
        }, this));
    }
    _.bind(xyz, this)(shortlist);
}

exports.KNode.prototype._iterativeFindNode = function(nodeID, cb) {
    this._iterativeFind(nodeID, 'NODE', cb);
}

// cb -> function(err, value)
// this does not map over directly to the spec
// rather iterativeFind already does the related things
// if the callback gets a list of contacts, it simply
// assumes the key does not exist in the DHT (atleast with
// available knowledge)
exports.KNode.prototype._iterativeFindValue = function(key, cb) {
    var callback = cb || function() {};
    this._iterativeFind(key, 'VALUE', _.bind(function(err, type, result) {
        if (type == 'VALUE')
            callback(null, result);
        else
            callback({
                'code': 'NOTFOUND',
                'key': key
            }, null);
    }, this));
}

exports.KNode.prototype.toString = function() {
    return "Node " + this.self.nodeID + ":" + this.self.address + ":" + this.self.port;
}

exports.KNode.prototype.debug = function() {
    console.log(this.toString());
    _(this._buckets).each(function(bucket, j) {
        //console.log("bucket", j, bucket.toString());
        console.log("bucket", j, bucket.size(), "peers");
    });
    console.log("store", this._storage);
}

/***** Public API *****/
exports.KNode.prototype.connect = function(address, port, cb) {
    var callback = cb || function() {};
    assert.ok(this.self.nodeID);
    var contact = util.make_contact(address, port);
    async.waterfall([
        _.bind(this._updateContact, this, contact),
        _.bind(this._iterativeFindNode, this, this.self.nodeID),
        _.bind(function(err, type, contacts, cb) {
            if (err) {
                callback(err);
                return;
            }
            // FIXME: Do we update buckets or does iterativeFindNode do it?
            var leastBucket = _.min(_.keys(this._buckets));
            var bucketsToRefresh = _.filter(_.keys(this._buckets), function(num) { return num >= leastBucket; });
            // TODO: do each refresh only after earlier finishes
            _.each(bucketsToRefresh, this._refreshBucket, this);
            // TODO: use async module to trigger callback when all done
            callback(null);
        }, this)
    ]);
}

exports.KNode.prototype.get = function(key, cb) {
    var callback = cb || function() {};
    this._iterativeFindValue(util.id(key), callback);
}

exports.KNode.prototype.set = function(key, value, cb) {
    var callback = cb || function() {};
    var message = this._MSG('STORE', {
        'key': util.id(key),
        'value': value
    });
    this._iterativeFindNode(util.id(key), _.bind(function(err, type, contacts) {
        if (err) {
            callback(err);
            return;
        }
        async.forEach(contacts, _.bind(function(contact, asyncCb) {
            this._rpc.send(contact, message, function() {
                // TODO handle error
                asyncCb(null);
            });
        }, this), callback);
    }, this));
}
<commit_msg>Get rid of PING dead code<commit_after>/*
 * Copyright (C) 2011-2012 by Nikhil Marathe <nsm.nikhil@gmail.com>
 * 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 node_constants = require('constants');
var async = require('async');
var constants = require('./constants');
var util = require('./util');
var rpc = require('./rpc');
var Bucket = require('./bucket').Bucket;
var _ = require('underscore');
_.str = require('underscore.string');

// abbreviate message utilities
var MC = util.message_contact;
var MID = util.message_rpcID;

exports.KNode = function(desc) {
    // TODO: probably want to persist nodeID
    this.self = _.defaults({ nodeID: util.nodeID(desc.address, desc.port) }, desc);
    Object.freeze(this.self);

    this._storage = {};
    // object treated as an array
    this._buckets = {};
    this._rpc = new rpc.RPC(this.self, _.bind(this._onMessage, this));

    // DEBUG
    setInterval(_.bind(this.debug, this), 5000);
}

exports.KNode.prototype._MSG = function(type, params) {
    // NOTE: always keep this.self last. This way users of _MSG
    // don't have to worry about accidentally overriding self properties
    return _.extend({ type: type}, params, this.self);
}

exports.KNode.prototype._onMessage = function(message) {
    var methodName = '_on' + _.str.titleize(_.str.camelize(message.type.toLowerCase()));
    var action = this[methodName];
    if (action) {
        this._updateContact(MC(message));
        _.bind(action, this)(message);
    }
    else
        console.warn("Unknown message", message);
}

exports.KNode.prototype._onPing = function(message) {
    // this can be made more intelligent such that
    // if an outgoing message is present, piggyback the pong
    // onto it rather than sending it separately
    this._rpc.send(MC(message), this._MSG('PONG', {'replyTo': MID(message)}));
}

exports.KNode.prototype._updateContact = function(contact, cb) {
    var callback = cb || function() {};
    var bucketIndex = util.bucketIndex(this.self.nodeID, contact.nodeID);
    assert.ok(bucketIndex < constants.B);
    if (!this._buckets[bucketIndex])
        this._buckets[bucketIndex] = new Bucket();

    var bucket = this._buckets[bucketIndex];
    contact.lastSeen = Date.now();

    var exists = bucket.contains(contact);
    if (exists) {
        // move to the end of the bucket
        bucket.remove(contact);
        bucket.add(contact);
        callback();
    }
    else if (bucket.size() < constants.K) {
        bucket.add(contact);
        callback();
    }
    else {
        this._rpc.send(bucket.get(0), this._MSG('PING'),
            _.bind(function(err, message) {
                if( err ) {
                    // add new contact, old one is dead
                    bucket.removeIndex(0);
                    bucket.add(contact);
                }
                else {
                }
                callback();
            }, this)
        );
    }
}

// TODO: handle large values which
// won't fit in single UDP packets
exports.KNode.prototype._onStore = function(message) {
    assert.equal(message.key.length, constants.B/4);
    this._storage[message.key] = _.clone(message.value);
    this._rpc.send(MC(message), this._MSG('STORE_REPLY', {
        'replyTo': MID(message),
        'status': true
    }));
}

// This is just to prevent Unknown message errors
exports.KNode.prototype._onStoreReply = function() {}

// debug methods FIXME: remove
exports.KNode.prototype._onGet = function(message) {
    this.get(message.key, function(err, val) {
        console.log('ONGET', val);
    });
}

exports.KNode.prototype._onFindValue = function(message) {
    assert.ok(message.key);
    assert.equal(message.key.length, constants.B/4);
    if (this._storage.hasOwnProperty(message.key)) {
        this._rpc.send(MC(message), this._MSG('FIND_VALUE_REPLY', {
            'replyTo': MID(message),
            'found': true,
            'value': this._storage[message.key]
        }));
    }
    else {
        var contacts = this._findClosestNodes(message.key, constants.K, MC(message).nodeID);

        this._rpc.send(MC(message), this._MSG('FIND_NODE_REPLY', {
            'replyTo': MID(message),
            'contacts': contacts
        }));
    }
}

exports.KNode.prototype._findClosestNodes = function(key, howMany, exclude) {
    var contacts = [];
    function addContact(contact) {
        if (contacts.length >= howMany)
            return;

        if (contact.nodeID == exclude)
            return;

        contacts.push(contact);
    }

    function addClosestFromBucket(bucket) {
        var distances = _.map(bucket.contacts(), function(contact) {
            return {
                distance: util.distance(contact.nodeID, key),
                contact: contact
            };
        });

        distances.sort(function(a, b) {
            return util.buffer_compare(a.distance, b.distance);
        });

        _(distances).chain()
         .first(howMany - contacts.length)
         .pluck('contact')
         .map(MC)
         .value()
         .forEach(addContact);
    }

    // first check the same bucket
    // what bucket would key go into, that is the closest
    // bucket we start from, hence bucketIndex
    // is with reference to self.nodeID
    var bucketIndex = util.bucketIndex(this.self.nodeID, key);
    if (this._buckets.hasOwnProperty(bucketIndex))
        addClosestFromBucket(this._buckets[bucketIndex]);

    var oldBucketIndex = bucketIndex;
    // then check buckets higher up
    while (contacts.length < howMany && bucketIndex < constants.B) {
        bucketIndex++;
        if (this._buckets.hasOwnProperty(bucketIndex))
            addClosestFromBucket(this._buckets[bucketIndex]);
    }

    // then check buckets lower down
    // since Kademlia does not define the search strategy, we can cheat
    // and use this strategy although it may not actually return the closest
    // FIXME: be intelligent to choose actual closest
    bucketIndex = oldBucketIndex;
    while (contacts.length < howMany && bucketIndex >= 0) {
        bucketIndex--;
        if (this._buckets.hasOwnProperty(bucketIndex))
            addClosestFromBucket(this._buckets[bucketIndex]);
    }
    return contacts;
}

exports.KNode.prototype._refreshBucket = function(bucketIndex) {
    var random = util.randomInBucketRangeBuffer(bucketIndex);
    this._iterativeFindNode(random.toString('hex'));
}

// this is a primitive operation, no network activity allowed
exports.KNode.prototype._onFindNode = function(message) {
    assert.ok(message.key);

    var contacts = this._findClosestNodes(message.key, constants.K, MC(message).nodeID);

    this._rpc.send(MC(message), this._MSG('FIND_NODE_REPLY', {
        'replyTo': MID(message),
        'contacts': contacts
    }));
}

// cb should be function(err, type, result)
// where type == 'VALUE' -> result is the value
//       type == 'NODE'  -> result is [list of contacts]
exports.KNode.prototype._iterativeFind = function(key, mode, cb) {
    assert.ok(_.include(['NODE', 'VALUE'], mode));
    var externalCallback = cb || function() {};

    var closestNode = null, previousClosestNode = null;
    var closestNodeDistance = -1;
    var shortlist = this._findClosestNodes(key, constants.ALPHA, this.self.nodeID);
    var contacted = {};
    var foundValue = false;
    var value = null;
    var contactsWhichHadValue = [];
    closestNode = shortlist[0];
    if (!closestNode) {
        // we aren't connected to the overlay network!
        externalCallback({ message: 'Not connected to overlay network. No peers.'}, mode, null);
        return;
    }
    closestNodeDistance = util.distance(key, closestNode.nodeID);

    function xyz(alphaContacts) {
        // clone because we're going to be modifying inside
        async.forEach(alphaContacts, _.bind(function(contact, callback) {
            this._rpc.send(contact, this._MSG('FIND_'+mode, {
                key: key
            }), _.bind(function(err, message) {
                if (err) {
                    console.log("ERROR in iterativeFind"+_.str.titleize(mode)+" send to", contact);
                    shortlist = _.reject(shortlist, function(el) { return el.nodeID == contact.nodeID; });
                }
                else {
                    this._updateContact(contact);
                    contacted[contact.nodeID] = true;
                    var dist = util.distance(key, contact.nodeID);
                    if (util.buffer_compare(dist, closestNodeDistance) == -1) {
                        previousClosestNode = closestNode;
                        closestNode = contact;
                        closestNodeDistance = dist;
                    }

                    if (message.found && mode == 'VALUE') {
                        // TODO: iterative find value when it does find the
                        // value should store the value at the closest node which
                        // DID NOT have the value
                        foundValue = true;
                        value = message.value;
                        contactsWhichHadValue.push(contact.nodeID);
                    }
                    else {
                        shortlist = shortlist.concat(message.contacts);
                        shortlist = _.uniq(shortlist, false /* is sorted? */, function(contact) {
                            return contact.nodeID;
                        });
                    }
                }
                callback();
            }, this));
        }, this), _.bind(function(err) {
            if (foundValue) {
                console.log("Found value from ", contactsWhichHadValue);
                // closest node without value

                var thisNodeID = this.self.nodeID;
                var contactsWithoutValue = _.reject(shortlist, function(contact) {
                    return _.include(contactsWhichHadValue, contact.nodeID);
                });

                var distances = _.map(contactsWithoutValue, function(contact) {
                    return {
                        distance: util.distance(contact.nodeID, thisNodeID),
                        contact: contact
                    };
                });

                distances.sort(function(a, b) {
                    return util.buffer_compare(a.distance, b.distance);
                });

                if (distances.length >= 1) {
                    var closestWithoutValue = distances[0].contact;

                    console.log("Closest is ", closestWithoutValue);
                    var message = this._MSG('STORE', {
                        'key': key,
                        'value': value
                    });
                    this._rpc.send(closestWithoutValue, message);
                }
                externalCallback(null, 'VALUE', value);
                return;
            }

            if (closestNode == previousClosestNode || shortlist.length >= constants.K) {
                // TODO: clarify we might have to do a FIND_NODE here too
                externalCallback(null, 'NODE', shortlist);
                return;
            }

            var remain = _.reject(shortlist, function(el) { return contacted[el.nodeID]; })
            // TODO: call only on alpha random
            if (remain.length == 0)
                externalCallback(null, 'NODE', shortlist);
            else
                _.bind(xyz, this)(remain);
        }, this));
    }
    _.bind(xyz, this)(shortlist);
}

exports.KNode.prototype._iterativeFindNode = function(nodeID, cb) {
    this._iterativeFind(nodeID, 'NODE', cb);
}

// cb -> function(err, value)
// this does not map over directly to the spec
// rather iterativeFind already does the related things
// if the callback gets a list of contacts, it simply
// assumes the key does not exist in the DHT (atleast with
// available knowledge)
exports.KNode.prototype._iterativeFindValue = function(key, cb) {
    var callback = cb || function() {};
    this._iterativeFind(key, 'VALUE', _.bind(function(err, type, result) {
        if (type == 'VALUE')
            callback(null, result);
        else
            callback({
                'code': 'NOTFOUND',
                'key': key
            }, null);
    }, this));
}

exports.KNode.prototype.toString = function() {
    return "Node " + this.self.nodeID + ":" + this.self.address + ":" + this.self.port;
}

exports.KNode.prototype.debug = function() {
    console.log(this.toString());
    _(this._buckets).each(function(bucket, j) {
        //console.log("bucket", j, bucket.toString());
        console.log("bucket", j, bucket.size(), "peers");
    });
    console.log("store", this._storage);
}

/***** Public API *****/
exports.KNode.prototype.connect = function(address, port, cb) {
    var callback = cb || function() {};
    assert.ok(this.self.nodeID);
    var contact = util.make_contact(address, port);
    async.waterfall([
        _.bind(this._updateContact, this, contact),
        _.bind(this._iterativeFindNode, this, this.self.nodeID),
        _.bind(function(err, type, contacts, cb) {
            if (err) {
                callback(err);
                return;
            }
            // FIXME: Do we update buckets or does iterativeFindNode do it?
            var leastBucket = _.min(_.keys(this._buckets));
            var bucketsToRefresh = _.filter(_.keys(this._buckets), function(num) { return num >= leastBucket; });
            // TODO: do each refresh only after earlier finishes
            _.each(bucketsToRefresh, this._refreshBucket, this);
            // TODO: use async module to trigger callback when all done
            callback(null);
        }, this)
    ]);
}

exports.KNode.prototype.get = function(key, cb) {
    var callback = cb || function() {};
    this._iterativeFindValue(util.id(key), callback);
}

exports.KNode.prototype.set = function(key, value, cb) {
    var callback = cb || function() {};
    var message = this._MSG('STORE', {
        'key': util.id(key),
        'value': value
    });
    this._iterativeFindNode(util.id(key), _.bind(function(err, type, contacts) {
        if (err) {
            callback(err);
            return;
        }
        async.forEach(contacts, _.bind(function(contact, asyncCb) {
            this._rpc.send(contact, message, function() {
                // TODO handle error
                asyncCb(null);
            });
        }, this), callback);
    }, this));
}
<|endoftext|>


<commit_before>'use strict';

/**
 * Stores system-wide HTTP configuration.
 */
const HTTP_CONFIG = {};

function _configure(options) {
    const request = {
        method: options.method,
        url: options.url || HTTP_CONFIG.host + options.resource,
        headers: {},
        withCredentials: false
    };

    if (typeof options.data === 'object') request.data = options.data;

    if (typeof HTTP_CONFIG.authorization === 'string') {
        request.withCredentials = true;
        request.headers['Authorization'] = HTTP_CONFIG.authorization;
    }

    if (typeof HTTP_CONFIG.accept === 'string') request.headers['Accept'] = HTTP_CONFIG.accept;

    if (typeof HTTP_CONFIG.contentType === 'string') request.headers['Content-Type'] = HTTP_CONFIG.contentType;

    if (typeof options.headers === 'object') {
        Object.keys(options.headers).forEach(function (item) {
            request.headers[item] = options.headers[item];
        });
    }

    if (typeof options.query === 'object') {
        const query = new URLSearchParams();

        for (let param in options.query) {
            if (options.query[param] !== null) {
                query.append(param, options.query[param]);
            }
        }

        request.url = `${request.url}?` + query.toString();
    }

    if (Object.keys(request.headers).length <= 0) delete request.headers;

    return request;
}

function _request(http) {
    return {
        request: function (options) {
            return http(_configure(options));
        },
        get: function (options) {
            options.method = 'GET';
            return http(_configure(options));
        },
        post: function (options) {
            options.method = 'POST';
            return http(_configure(options));
        },
        put: function (options) {
            options.method = 'PUT';
            return http(_configure(options));
        },
        patch: function (options) {
            options.method = 'PATCH';
            return http(_configure(options));
        },
        delete: function (options) {
            options.method = 'DELETE';
            return http(_configure(options));
        },
        setHost: function (host) {
            HTTP_CONFIG.host = host;
        } /*,
        basicAuth: function (username, password) {
            if (!username) return;
            httpConfig.authorization = 'Basic ' + $base64.encode(username + ':' + (password || ''));
        }*/
    };
}

function AJAXProvider(base64) {
    this.setHost = (host) => {
        HTTP_CONFIG.host = host;
    };

    this.basicAuth = (username, password) => {
        HTTP_CONFIG.authorization = 'Basic ' + base64.encode(username + ':' + (password || ''));
    };

    this.accept = (type) => {
        HTTP_CONFIG.accept = type;
    };

    this.contentType = (type) => {
        HTTP_CONFIG.contentType = type;
    };

    this.$get = ['$http', _request];
}

export default AJAXProvider;
<commit_msg>TODO: Implement auth methods<commit_after>/**
 * Copyright (c) Ajay Sreedhar. All rights reserved.
 *
 * Licensed under the MIT License.
 * Please see LICENSE file located in the project root for more information.
 */

'use strict';

/**
 * Stores system-wide HTTP configuration.
 */
const HTTP_CONFIG = {};

function _configure(options) {
    const request = {
        method: options.method,
        url: options.url || HTTP_CONFIG.host + options.resource,
        headers: {},
        withCredentials: false
    };

    if (typeof options.data === 'object') request.data = options.data;

    if (typeof HTTP_CONFIG.authorization === 'string') {
        request.withCredentials = true;
        request.headers['Authorization'] = HTTP_CONFIG.authorization;
    }

    if (typeof HTTP_CONFIG.accept === 'string') request.headers['Accept'] = HTTP_CONFIG.accept;

    if (typeof HTTP_CONFIG.contentType === 'string') request.headers['Content-Type'] = HTTP_CONFIG.contentType;

    if (typeof options.headers === 'object') {
        Object.keys(options.headers).forEach(function (item) {
            request.headers[item] = options.headers[item];
        });
    }

    if (typeof options.query === 'object') {
        const query = new URLSearchParams();

        for (let param in options.query) {
            if (options.query[param] !== null) {
                query.append(param, options.query[param]);
            }
        }

        request.url = `${request.url}?` + query.toString();
    }

    if (Object.keys(request.headers).length <= 0) delete request.headers;

    return request;
}

function _request(http) {
    return {
        request: function (options) {
            return http(_configure(options));
        },
        get: function (options) {
            options.method = 'GET';
            return http(_configure(options));
        },
        post: function (options) {
            options.method = 'POST';
            return http(_configure(options));
        },
        put: function (options) {
            options.method = 'PUT';
            return http(_configure(options));
        },
        patch: function (options) {
            options.method = 'PATCH';
            return http(_configure(options));
        },
        delete: function (options) {
            options.method = 'DELETE';
            return http(_configure(options));
        },
        setHost: function (host) {
            HTTP_CONFIG.host = host;
        } /*,
        basicAuth: function (username, password) {
            if (!username) return;
            httpConfig.authorization = 'Basic ' + $base64.encode(username + ':' + (password || ''));
        }*/
    };
}

function AJAXProvider(base64) {
    this.setHost = (host) => {
        HTTP_CONFIG.host = host;
    };

    this.basicAuth = (username, password) => {
        HTTP_CONFIG.authorization = 'Basic ' + base64.encode(username + ':' + (password || ''));
    };

    this.accept = (type) => {
        HTTP_CONFIG.accept = type;
    };

    this.contentType = (type) => {
        HTTP_CONFIG.contentType = type;
    };

    this.$get = ['$http', _request];
}

export default AJAXProvider;
<|endoftext|>


<commit_before>import classNames from "classnames";
import qs from "query-string";
import mixin from "reactjs-mixin";
import { Link, routerShape } from "react-router";
/* eslint-disable no-unused-vars */
import React from "react";
/* eslint-enable no-unused-vars */
import { StoreMixin } from "mesosphere-shared-reactjs";
import { Dropdown, Tooltip, Modal } from "reactjs-components";

import Icon from "#SRC/js/components/Icon";
import Breadcrumb from "../../components/Breadcrumb";
import BreadcrumbTextContent from "../../components/BreadcrumbTextContent";
import CosmosPackagesStore from "../../stores/CosmosPackagesStore";
import defaultServiceImage
  from "../../../../plugins/services/src/img/icon-service-default-large@2x.png";
import Image from "../../components/Image";
import ImageViewer from "../../components/ImageViewer";
import Loader from "../../components/Loader";
import MetadataStore from "../../stores/MetadataStore";
import Page from "../../components/Page";
import RequestErrorMsg from "../../components/RequestErrorMsg";
import StringUtil from "../../utils/StringUtil";

const PackageDetailBreadcrumbs = ({ cosmosPackage }) => {
  const name = cosmosPackage.getName();
  const version = cosmosPackage.getVersion();

  const crumbs = [
    <Breadcrumb key={0} title="Catalog">
      <BreadcrumbTextContent>
        <Link to="/catalog/packages">Catalog</Link>
      </BreadcrumbTextContent>
    </Breadcrumb>,
    <Breadcrumb key={1} title={name}>
      <BreadcrumbTextContent>
        <Link to={`/catalog/packages/${name}`} query={{ version }} key={0}>
          {name}
        </Link>
      </BreadcrumbTextContent>
    </Breadcrumb>
  ];

  return <Page.Header.Breadcrumbs iconID="catalog" breadcrumbs={crumbs} />;
};

const METHODS_TO_BIND = [
  "handlePackageVersionChange",
  "handleReviewAndRunClick",
  "onInstalledSuccessModalClose"
];

class PackageDetailTab extends mixin(StoreMixin) {
  constructor() {
    super(...arguments);

    this.state = {
      hasError: 0,
      isLoadingSelectedVersion: false,
      isLoadingVersions: false
    };

    this.store_listeners = [
      {
        name: "cosmosPackages",
        events: [
          "packageDescriptionError",
          "packageDescriptionSuccess",
          "listVersionsSuccess",
          "listVersionsError"
        ],
        suppressUpdate: true
      }
    ];

    METHODS_TO_BIND.forEach(method => {
      this[method] = this[method].bind(this);
    });
  }

  retrievePackageInfo(packageName, version) {
    const cosmosPackage = CosmosPackagesStore.getPackageDetails();
    const packageVersions = CosmosPackagesStore.getPackageVersions(packageName);

    // Fetch package versions if necessary
    if (packageVersions == null) {
      this.setState({ isLoadingVersions: true });

      CosmosPackagesStore.fetchPackageVersions(packageName);
    }
    // Fetch new description if name or version changed
    if (
      cosmosPackage == null ||
      packageName !== cosmosPackage.getName() ||
      version !== cosmosPackage.getVersion()
    ) {
      this.setState({ isLoadingSelectedVersion: true });

      CosmosPackagesStore.fetchPackageDescription(packageName, version);
    }
  }

  componentDidMount() {
    super.componentDidMount(...arguments);

    this.retrievePackageInfo(
      this.props.params.packageName,
      this.props.location.query.version
    );
  }

  componentWillReceiveProps(nextProps) {
    super.componentWillReceiveProps(...arguments);

    this.retrievePackageInfo(
      nextProps.params.packageName,
      nextProps.location.query.version
    );
  }

  onCosmosPackagesStorePackageDescriptionError() {
    this.setState({ hasError: true });
  }

  onCosmosPackagesStorePackageDescriptionSuccess() {
    this.setState({
      hasError: false,
      isLoadingSelectedVersion: false
    });
  }

  onCosmosPackagesStoreListVersionsSuccess() {
    this.setState({ isLoadingVersions: false });
  }

  handleReviewAndRunClick() {
    const { router } = this.context;
    const { params, location } = this.props;

    router.push(
      `/catalog/packages/${encodeURIComponent(params.packageName)}/deploy?${location.query.version}`
    );
  }

  handlePackageVersionChange(selection) {
    const query = Object.assign({}, this.props.location.query, {
      version: selection.id
    });

    global.location.replace(
      `#${this.props.location.pathname}?${qs.stringify(query)}`
    );
  }

  getErrorScreen() {
    return <RequestErrorMsg />;
  }

  getItems(definition, renderItem) {
    const items = [];
    definition.forEach((item, index) => {
      const { label, type, value } = item;

      // When there is no content to render, discard it all together
      if (!value || (Array.isArray(value) && !value.length)) {
        return null;
      }

      // If not specific type assume value is renderable
      let content = value;

      // Render sub items
      if (type === "subItems") {
        content = this.getItems(value, this.getSubItem);
      }

      items.push(renderItem(label, content, index));
    });

    return items;
  }

  getItem(label, value, key) {
    if (!label || !value) {
      return null;
    }

    if (typeof value === "string") {
      value = (

          {value}

      );
    }

    return (

          {label}

        {value}

    );
  }

  getLoadingScreen() {
    return <Loader />;
  }

  getSubItem(label, value, key) {
    let content = value;

    if (StringUtil.isEmail(value)) {
      content = (
        <a key={key} href={`mailto:${value}`}>
          {value}

      );
    }

    if (StringUtil.isUrl(value)) {
      content = (

          {value}

      );
    }

    return (

        {`${label}: `}
        {content}

    );
  }

  mapLicenses(licenses) {
    return licenses.map(function(license) {
      const item = {
        label: license.name,
        value: license.url
      };

      return item;
    });
  }

  getPackageBadge(cosmosPackage) {
    const isCertified = cosmosPackage.isCertified();
    const badgeCopy = isCertified ? "Certified" : "Community";
    const badgeClasses = classNames("badge badge-large badge-rounded", {
      "badge--primary": isCertified
    });

    return (

          {badgeCopy}

    );
  }

  getInstallButtons(cosmosPackage) {
    const tooltipContent = "Loading selected version";

    if (cosmosPackage.isCLIOnly()) {
      return (

CLI Only Package

            {"This package can only be installed using the CLI. See the "}

              documentation
            .

      );
    }

    const { isLoadingSelectedVersion } = this.state;

    return (

        <Tooltip
          wrapperClassName="button-group"
          wrapText={true}
          content={tooltipContent}
          suppress={!isLoadingSelectedVersion}
          width={200}
        >
          <button
            disabled={isLoadingSelectedVersion}
            className="button button-primary"
            onClick={this.handleReviewAndRunClick}
          >
            Review & Run
          </button>
        </Tooltip>

    );
  }

  getPackageVersionsDropdown() {
    const cosmosPackage = CosmosPackagesStore.getPackageDetails();
    const packageName = cosmosPackage.getName();
    const packageVersions = CosmosPackagesStore.getPackageVersions(packageName);

    if (packageVersions == null) {
      return null;
    }

    const selectedVersion = cosmosPackage.getVersion();
    const availableVersions = packageVersions.getVersions().map(version => {
      return {
        html: version,
        id: version
      };
    });

    if (availableVersions.length === 0) {
      return null;
    }

    return (
      <Dropdown
        buttonClassName="button button-link dropdown-toggle"
        dropdownMenuClassName="dropdown-menu"
        dropdownMenuListClassName="dropdown-menu-list"
        onItemSelection={this.handlePackageVersionChange}
        items={availableVersions}
        persistentID={selectedVersion}
        transition={true}
        wrapperClassName="dropdown"
      />
    );
  }

  getPackageDescription(definition, cosmosPackage) {
    return (

        {this.getItems(definition, this.getItem)}
        <ImageViewer images={cosmosPackage.getScreenshots()} />

    );
  }

  onInstalledSuccessModalClose() {
    const query = Object.assign({}, this.props.location.query);
    delete query.appId;

    global.location.replace(
      `#${this.props.location.pathname}?${qs.stringify(query)}`
    );
  }

  getInstalledSuccessModal(name) {
    const { location } = this.props;

    return (
      <Modal
        modalClass={"modal modal-small"}
        open={!!location.query.appId}
        onClose={this.onInstalledSuccessModalClose}
      >

                <Icon id="circle-check" size="large" color="green" />

Success!

                {`${StringUtil.capitalize(name)} is being installed.`}

              <a
                className="button button-success button-block"
                href={`#/services/detail/${encodeURIComponent(location.query.appId)}`}
              >
                Open Service

      </Modal>
    );
  }

  render() {
    const { props, state } = this;

    if (state.hasError || !props.params.packageName) {
      return this.getErrorScreen();
    }

    const cosmosPackage = CosmosPackagesStore.getPackageDetails();
    if (!cosmosPackage) {
      return this.getLoadingScreen();
    }

    const name = cosmosPackage.getName();
    const description = cosmosPackage.getDescription();
    const preInstallNotes = cosmosPackage.getPreInstallNotes();

    let preInstallNotesParsed = null;
    if (preInstallNotes) {
      preInstallNotesParsed = StringUtil.parseMarkdown(preInstallNotes);
      preInstallNotesParsed.__html =
        "Preinstall Notes: " + preInstallNotesParsed.__html;
    }

    const definition = [
      {
        label: "Description",
        value: description &&

      },
      {
        label: " ",
        value: preInstallNotes &&

      },
      {
        label: "Information",
        type: "subItems",
        value: [
          { label: "SCM", value: cosmosPackage.getSCM() },
          { label: "Maintainer", value: cosmosPackage.getMaintainer() }
        ]
      },
      {
        label: "Licenses",
        type: "subItems",
        value: this.mapLicenses(cosmosPackage.getLicenses())
      }
    ];

    return (
      <Page>
        <Page.Header
          breadcrumbs={
            <PackageDetailBreadcrumbs cosmosPackage={cosmosPackage} />
          }
        />

                  <Image
                    fallbackSrc={defaultServiceImage}
                    src={cosmosPackage.getIcons()["icon-large"]}
                  />

                    {name}

                  {this.getPackageVersionsDropdown()}

                  {this.getPackageBadge(cosmosPackage)}

                {this.getInstallButtons(cosmosPackage)}

          {state.isLoadingSelectedVersion
            ? this.getLoadingScreen()
            : this.getPackageDescription(definition, cosmosPackage)}

        {this.getInstalledSuccessModal(name)}
      </Page>
    );
  }
}

PackageDetailTab.contextTypes = {
  router: routerShape
};

module.exports = PackageDetailTab;
<commit_msg>style(badge): Change badge style on package detail view<commit_after>import classNames from "classnames";
import qs from "query-string";
import mixin from "reactjs-mixin";
import { Link, routerShape } from "react-router";
/* eslint-disable no-unused-vars */
import React from "react";
/* eslint-enable no-unused-vars */
import { StoreMixin } from "mesosphere-shared-reactjs";
import { Dropdown, Tooltip, Modal } from "reactjs-components";

import Icon from "#SRC/js/components/Icon";
import Breadcrumb from "../../components/Breadcrumb";
import BreadcrumbTextContent from "../../components/BreadcrumbTextContent";
import CosmosPackagesStore from "../../stores/CosmosPackagesStore";
import defaultServiceImage
  from "../../../../plugins/services/src/img/icon-service-default-large@2x.png";
import Image from "../../components/Image";
import ImageViewer from "../../components/ImageViewer";
import Loader from "../../components/Loader";
import MetadataStore from "../../stores/MetadataStore";
import Page from "../../components/Page";
import RequestErrorMsg from "../../components/RequestErrorMsg";
import StringUtil from "../../utils/StringUtil";

const PackageDetailBreadcrumbs = ({ cosmosPackage }) => {
  const name = cosmosPackage.getName();
  const version = cosmosPackage.getVersion();

  const crumbs = [
    <Breadcrumb key={0} title="Catalog">
      <BreadcrumbTextContent>
        <Link to="/catalog/packages">Catalog</Link>
      </BreadcrumbTextContent>
    </Breadcrumb>,
    <Breadcrumb key={1} title={name}>
      <BreadcrumbTextContent>
        <Link to={`/catalog/packages/${name}`} query={{ version }} key={0}>
          {name}
        </Link>
      </BreadcrumbTextContent>
    </Breadcrumb>
  ];

  return <Page.Header.Breadcrumbs iconID="catalog" breadcrumbs={crumbs} />;
};

const METHODS_TO_BIND = [
  "handlePackageVersionChange",
  "handleReviewAndRunClick",
  "onInstalledSuccessModalClose"
];

class PackageDetailTab extends mixin(StoreMixin) {
  constructor() {
    super(...arguments);

    this.state = {
      hasError: 0,
      isLoadingSelectedVersion: false,
      isLoadingVersions: false
    };

    this.store_listeners = [
      {
        name: "cosmosPackages",
        events: [
          "packageDescriptionError",
          "packageDescriptionSuccess",
          "listVersionsSuccess",
          "listVersionsError"
        ],
        suppressUpdate: true
      }
    ];

    METHODS_TO_BIND.forEach(method => {
      this[method] = this[method].bind(this);
    });
  }

  retrievePackageInfo(packageName, version) {
    const cosmosPackage = CosmosPackagesStore.getPackageDetails();
    const packageVersions = CosmosPackagesStore.getPackageVersions(packageName);

    // Fetch package versions if necessary
    if (packageVersions == null) {
      this.setState({ isLoadingVersions: true });

      CosmosPackagesStore.fetchPackageVersions(packageName);
    }
    // Fetch new description if name or version changed
    if (
      cosmosPackage == null ||
      packageName !== cosmosPackage.getName() ||
      version !== cosmosPackage.getVersion()
    ) {
      this.setState({ isLoadingSelectedVersion: true });

      CosmosPackagesStore.fetchPackageDescription(packageName, version);
    }
  }

  componentDidMount() {
    super.componentDidMount(...arguments);

    this.retrievePackageInfo(
      this.props.params.packageName,
      this.props.location.query.version
    );
  }

  componentWillReceiveProps(nextProps) {
    super.componentWillReceiveProps(...arguments);

    this.retrievePackageInfo(
      nextProps.params.packageName,
      nextProps.location.query.version
    );
  }

  onCosmosPackagesStorePackageDescriptionError() {
    this.setState({ hasError: true });
  }

  onCosmosPackagesStorePackageDescriptionSuccess() {
    this.setState({
      hasError: false,
      isLoadingSelectedVersion: false
    });
  }

  onCosmosPackagesStoreListVersionsSuccess() {
    this.setState({ isLoadingVersions: false });
  }

  handleReviewAndRunClick() {
    const { router } = this.context;
    const { params, location } = this.props;

    router.push(
      `/catalog/packages/${encodeURIComponent(params.packageName)}/deploy?${location.query.version}`
    );
  }

  handlePackageVersionChange(selection) {
    const query = Object.assign({}, this.props.location.query, {
      version: selection.id
    });

    global.location.replace(
      `#${this.props.location.pathname}?${qs.stringify(query)}`
    );
  }

  getErrorScreen() {
    return <RequestErrorMsg />;
  }

  getItems(definition, renderItem) {
    const items = [];
    definition.forEach((item, index) => {
      const { label, type, value } = item;

      // When there is no content to render, discard it all together
      if (!value || (Array.isArray(value) && !value.length)) {
        return null;
      }

      // If not specific type assume value is renderable
      let content = value;

      // Render sub items
      if (type === "subItems") {
        content = this.getItems(value, this.getSubItem);
      }

      items.push(renderItem(label, content, index));
    });

    return items;
  }

  getItem(label, value, key) {
    if (!label || !value) {
      return null;
    }

    if (typeof value === "string") {
      value = (

          {value}

      );
    }

    return (

          {label}

        {value}

    );
  }

  getLoadingScreen() {
    return <Loader />;
  }

  getSubItem(label, value, key) {
    let content = value;

    if (StringUtil.isEmail(value)) {
      content = (
        <a key={key} href={`mailto:${value}`}>
          {value}

      );
    }

    if (StringUtil.isUrl(value)) {
      content = (

          {value}

      );
    }

    return (

        {`${label}: `}
        {content}

    );
  }

  mapLicenses(licenses) {
    return licenses.map(function(license) {
      const item = {
        label: license.name,
        value: license.url
      };

      return item;
    });
  }

  getPackageBadge(cosmosPackage) {
    const isCertified = cosmosPackage.isCertified();
    const badgeCopy = isCertified ? "Certified" : "Community";
    const badgeClasses = classNames("badge badge-large", {
      "badge--primary": isCertified
    });

    return (

          {badgeCopy}

    );
  }

  getInstallButtons(cosmosPackage) {
    const tooltipContent = "Loading selected version";

    if (cosmosPackage.isCLIOnly()) {
      return (

CLI Only Package

            {"This package can only be installed using the CLI. See the "}

              documentation
            .

      );
    }

    const { isLoadingSelectedVersion } = this.state;

    return (

        <Tooltip
          wrapperClassName="button-group"
          wrapText={true}
          content={tooltipContent}
          suppress={!isLoadingSelectedVersion}
          width={200}
        >
          <button
            disabled={isLoadingSelectedVersion}
            className="button button-primary"
            onClick={this.handleReviewAndRunClick}
          >
            Review & Run
          </button>
        </Tooltip>

    );
  }

  getPackageVersionsDropdown() {
    const cosmosPackage = CosmosPackagesStore.getPackageDetails();
    const packageName = cosmosPackage.getName();
    const packageVersions = CosmosPackagesStore.getPackageVersions(packageName);

    if (packageVersions == null) {
      return null;
    }

    const selectedVersion = cosmosPackage.getVersion();
    const availableVersions = packageVersions.getVersions().map(version => {
      return {
        html: version,
        id: version
      };
    });

    if (availableVersions.length === 0) {
      return null;
    }

    return (
      <Dropdown
        buttonClassName="button button-link dropdown-toggle"
        dropdownMenuClassName="dropdown-menu"
        dropdownMenuListClassName="dropdown-menu-list"
        onItemSelection={this.handlePackageVersionChange}
        items={availableVersions}
        persistentID={selectedVersion}
        transition={true}
        wrapperClassName="dropdown"
      />
    );
  }

  getPackageDescription(definition, cosmosPackage) {
    return (

        {this.getItems(definition, this.getItem)}
        <ImageViewer images={cosmosPackage.getScreenshots()} />

    );
  }

  onInstalledSuccessModalClose() {
    const query = Object.assign({}, this.props.location.query);
    delete query.appId;

    global.location.replace(
      `#${this.props.location.pathname}?${qs.stringify(query)}`
    );
  }

  getInstalledSuccessModal(name) {
    const { location } = this.props;

    return (
      <Modal
        modalClass={"modal modal-small"}
        open={!!location.query.appId}
        onClose={this.onInstalledSuccessModalClose}
      >

                <Icon id="circle-check" size="large" color="green" />

Success!

                {`${StringUtil.capitalize(name)} is being installed.`}

              <a
                className="button button-success button-block"
                href={`#/services/detail/${encodeURIComponent(location.query.appId)}`}
              >
                Open Service

      </Modal>
    );
  }

  render() {
    const { props, state } = this;

    if (state.hasError || !props.params.packageName) {
      return this.getErrorScreen();
    }

    const cosmosPackage = CosmosPackagesStore.getPackageDetails();
    if (!cosmosPackage) {
      return this.getLoadingScreen();
    }

    const name = cosmosPackage.getName();
    const description = cosmosPackage.getDescription();
    const preInstallNotes = cosmosPackage.getPreInstallNotes();

    let preInstallNotesParsed = null;
    if (preInstallNotes) {
      preInstallNotesParsed = StringUtil.parseMarkdown(preInstallNotes);
      preInstallNotesParsed.__html =
        "Preinstall Notes: " + preInstallNotesParsed.__html;
    }

    const definition = [
      {
        label: "Description",
        value: description &&

      },
      {
        label: " ",
        value: preInstallNotes &&

      },
      {
        label: "Information",
        type: "subItems",
        value: [
          { label: "SCM", value: cosmosPackage.getSCM() },
          { label: "Maintainer", value: cosmosPackage.getMaintainer() }
        ]
      },
      {
        label: "Licenses",
        type: "subItems",
        value: this.mapLicenses(cosmosPackage.getLicenses())
      }
    ];

    return (
      <Page>
        <Page.Header
          breadcrumbs={
            <PackageDetailBreadcrumbs cosmosPackage={cosmosPackage} />
          }
        />

                  <Image
                    fallbackSrc={defaultServiceImage}
                    src={cosmosPackage.getIcons()["icon-large"]}
                  />

                    {name}

                  {this.getPackageVersionsDropdown()}

                  {this.getPackageBadge(cosmosPackage)}

                {this.getInstallButtons(cosmosPackage)}

          {state.isLoadingSelectedVersion
            ? this.getLoadingScreen()
            : this.getPackageDescription(definition, cosmosPackage)}

        {this.getInstalledSuccessModal(name)}
      </Page>
    );
  }
}

PackageDetailTab.contextTypes = {
  router: routerShape
};

module.exports = PackageDetailTab;
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2011 by Nikhil Marathe <nsm.nikhil@gmail.com>
 * 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 node_constants = require('constants');
var constants = require('./constants');
var util = require('./util');
var rpc = require('./rpc');
var Bucket = require('./bucket').Bucket;
var _ = require('underscore');
_.str = require('underscore.string');

// abbreviate message utilities
var MC = util.message_contact;
var MID = util.message_rpcID;

exports.KNode = function(desc) {
    // TODO: probably want to persist nodeID
    this.self = _.defaults({ nodeID: util.nodeID(desc.address, desc.port) }, desc);
    Object.freeze(this.self);

    this._storage = {};
    // object treated as an array
    this._buckets = {};
    this._rpc = new rpc.RPC(this.self, _.bind(this._onMessage, this));

    // DEBUG
    setInterval(_.bind(this.debug, this), 5000);
}

exports.KNode.prototype._MSG = function(type, params) {
    // NOTE: always keep this.self last. This way users of _MSG
    // don't have to worry about accidentally overriding self properties
    return _.extend({ type: type}, params, this.self);
}

exports.KNode.prototype._onMessage = function(message) {
    var methodName = '_on' + _.str.titleize(_.str.camelize(message.type.toLowerCase()));
    var action = this[methodName];
    if (action) {
        this._updateContact(MC(message));
        _.bind(action, this)(message);
    }
    else
        console.warn("Unknown message", message);
}

exports.KNode.prototype._ping = function(contact) {

    function onPong(err, message) {
        if (err && err.errno == node_constants.ETIMEDOUT) {
            // TODO: handle peer death
        }
        else {
            assert.equal(message.type, 'PONG');
            this._updateContact(MC(message));
        }
    }

    this._rpc.send(contact, this._MSG('PING'), _.bind(onPong, this));
}

exports.KNode.prototype._onPing = function(message) {
    // this can be made more intelligent such that
    // if an outgoing message is present, piggyback the pong
    // onto it rather than sending it separately
    this._rpc.send(MC(message), this._MSG('PONG', {'replyTo': MID(message)}));
}

exports.KNode.prototype._updateContact = function(contact, cb) {
    var callback = cb || function() {};
    var bucketIndex = util.bucketIndex(this.self.nodeID, contact.nodeID);
    assert.ok(bucketIndex < constants.B);
    if (!this._buckets[bucketIndex])
        this._buckets[bucketIndex] = new Bucket();

    var bucket = this._buckets[bucketIndex];
    contact.lastSeen = Date.now();

    var exists = bucket.contains(contact);
    if( exists ) {
        // move to the end of the bucket
        bucket.remove(contact);
        bucket.add(contact);
        callback();
    }
    else if (bucket.size() < 8 /*constants.K*/) { // TODO: FIXME: use constants.K
        bucket.add(contact);
        callback();
    }
    else { // TODO:
        this._rpc.send(bucket.get(0), this._MSG('PING'),
            _.bind(function(err, message) {
                if( err ) {
                    // add new contact, old one is dead
                    console.log("Bucket", bucketIndex, "Booted", bucket.get(0), "added", contact);
                    bucket.removeIndex(0);
                    bucket.add(contact);
                }
                else {
                    console.log("Bucket", bucketIndex, "Keeping existing contact");
                }
                callback();
            }), this);
    }
}

// TODO: handle large values which
// won't fit in single UDP packets
exports.KNode.prototype._onStore = function(message) {
    assert.equal(message.key.length, constants.B/4);
    this._storage[message.key] = _.clone(message.value);
    this._rpc.send(MC(message), this._MSG('STORE_REPLY', {
        'replyTo': MID(message),
        'status': true
    }));
}

exports.KNode.prototype._onFindValue = function(message) {
    assert.equal(message.key.length, constants.B/4);
    if (this._storage.hasOwnProperty(message.key)) {
        this._rpc.send(MC(message), this._MSG('FIND_VALUE_REPLY', {
            'replyTo': MID(message),
            'found': true,
            'value': this._storage[message.key]
        }));
    }
    else {
        this._rpc.send(MC(message), this._MSG('FIND_VALUE_REPLY', {
            'replyTo': MID(message),
            'found': false
        }));
    }

    // TODO: otherwise this is just a FIND_NODE
}

exports.KNode.prototype.toString = function() {
    return "Node " + this.self.nodeID + ":" + this.self.address + ":" + this.self.port;
}
exports.KNode.prototype.debug = function() {
    console.log(this.toString());
    _(this._buckets).each(function(bucket, j) {
        //console.log("bucket", j, bucket.toString());
        console.log("bucket", j, bucket.size(), "peers");
    });
}
<commit_msg>Untested: Added primitive onFindNode and findClosestNode<commit_after>/*
 * Copyright (C) 2011 by Nikhil Marathe <nsm.nikhil@gmail.com>
 * 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 node_constants = require('constants');
var constants = require('./constants');
var util = require('./util');
var rpc = require('./rpc');
var Bucket = require('./bucket').Bucket;
var _ = require('underscore');
_.str = require('underscore.string');

// abbreviate message utilities
var MC = util.message_contact;
var MID = util.message_rpcID;

exports.KNode = function(desc) {
    // TODO: probably want to persist nodeID
    this.self = _.defaults({ nodeID: util.nodeID(desc.address, desc.port) }, desc);
    Object.freeze(this.self);

    this._storage = {};
    // object treated as an array
    this._buckets = {};
    this._rpc = new rpc.RPC(this.self, _.bind(this._onMessage, this));

    // DEBUG
    setInterval(_.bind(this.debug, this), 5000);
}

exports.KNode.prototype._MSG = function(type, params) {
    // NOTE: always keep this.self last. This way users of _MSG
    // don't have to worry about accidentally overriding self properties
    return _.extend({ type: type}, params, this.self);
}

exports.KNode.prototype._onMessage = function(message) {
    var methodName = '_on' + _.str.titleize(_.str.camelize(message.type.toLowerCase()));
    var action = this[methodName];
    if (action) {
        this._updateContact(MC(message));
        _.bind(action, this)(message);
    }
    else
        console.warn("Unknown message", message);
}

exports.KNode.prototype._ping = function(contact) {

    function onPong(err, message) {
        if (err && err.errno == node_constants.ETIMEDOUT) {
            // TODO: handle peer death
        }
        else {
            assert.equal(message.type, 'PONG');
            this._updateContact(MC(message));
        }
    }

    this._rpc.send(contact, this._MSG('PING'), _.bind(onPong, this));
}

exports.KNode.prototype._onPing = function(message) {
    // this can be made more intelligent such that
    // if an outgoing message is present, piggyback the pong
    // onto it rather than sending it separately
    this._rpc.send(MC(message), this._MSG('PONG', {'replyTo': MID(message)}));
}

exports.KNode.prototype._updateContact = function(contact, cb) {
    var callback = cb || function() {};
    var bucketIndex = util.bucketIndex(this.self.nodeID, contact.nodeID);
    assert.ok(bucketIndex < constants.B);
    if (!this._buckets[bucketIndex])
        this._buckets[bucketIndex] = new Bucket();

    var bucket = this._buckets[bucketIndex];
    contact.lastSeen = Date.now();

    var exists = bucket.contains(contact);
    if( exists ) {
        // move to the end of the bucket
        bucket.remove(contact);
        bucket.add(contact);
        callback();
    }
    else if (bucket.size() < 8 /*constants.K*/) { // TODO: FIXME: use constants.K
        bucket.add(contact);
        callback();
    }
    else { // TODO:
        this._rpc.send(bucket.get(0), this._MSG('PING'),
            _.bind(function(err, message) {
                if( err ) {
                    // add new contact, old one is dead
                    console.log("Bucket", bucketIndex, "Booted", bucket.get(0), "added", contact);
                    bucket.removeIndex(0);
                    bucket.add(contact);
                }
                else {
                    console.log("Bucket", bucketIndex, "Keeping existing contact");
                }
                callback();
            }), this);
    }
}

// TODO: handle large values which
// won't fit in single UDP packets
exports.KNode.prototype._onStore = function(message) {
    assert.equal(message.key.length, constants.B/4);
    this._storage[message.key] = _.clone(message.value);
    this._rpc.send(MC(message), this._MSG('STORE_REPLY', {
        'replyTo': MID(message),
        'status': true
    }));
}

exports.KNode.prototype._onFindValue = function(message) {
    assert.equal(message.key.length, constants.B/4);
    if (this._storage.hasOwnProperty(message.key)) {
        this._rpc.send(MC(message), this._MSG('FIND_VALUE_REPLY', {
            'replyTo': MID(message),
            'found': true,
            'value': this._storage[message.key]
        }));
    }
    else {
        this._rpc.send(MC(message), this._MSG('FIND_VALUE_REPLY', {
            'replyTo': MID(message),
            'found': false
        }));
    }

    // TODO: otherwise this is just a FIND_NODE
}

exports.KNode.prototype._findClosestNodes = function(key, howMany, exclude) {
    var contacts = [];
    function addContact(contact) {
        if (contacts.length >= howMany)
            return;

        if (contact.nodeID == exclude)
            return;

        contacts.push(contact);
    }

    function addClosestFromBucket(bucket) {
        var distances = _.map(bucket.contacts(), function(contact) {
            return {
                distance: util.distance(contact.nodeID, key),
                contact: contact
            };
        });

        distances.sort(function(a, b) {
            return util.buffer_compare(a.distance, b.distance);
        });

        _.first(distances, howMany - contacts.length).forEach(addContact);

    }

    // first check the same bucket
    // what bucket would key go into, that is the closest
    // bucket we start from, hence bucketIndex
    // is with reference to self.nodeID
    var bucketIndex = util.bucketIndex(this.self.nodeID, key);
    if (this._buckets.hasOwnProperty(bucketIndex))
        addClosestFromBucket(this._buckets[bucketIndex]);

    // then check buckets higher up
    while (contacts.length < howMany && bucketIndex < constants.B) {
        bucketIndex++;
        if (this._buckets.hasOwnProperty(bucketIndex))
            addClosestFromBucket(this._buckets[bucketIndex]);
    }

    console.log("By distance", contacts);
    return _(contacts).chain().pluck('contact').map(MC).value();
}

// this is a primitive operation, no network activity allowed
exports.KNode.prototype._onFindNode = function(message) {
    assert.ok(message.key);

    var contacts = this._findClosestNodes(message.key, constants.K, MC(message).nodeID);

    this._rpc.send(MC(message), this._MSG('FIND_NODE_REPLY', {
        'replyTo': MID(message),
        'success': true,
        'contacts': contacts
    }));
}

exports.KNode.prototype.toString = function() {
    return "Node " + this.self.nodeID + ":" + this.self.address + ":" + this.self.port;
}

exports.KNode.prototype.debug = function() {
    console.log(this.toString());
    _(this._buckets).each(function(bucket, j) {
        //console.log("bucket", j, bucket.toString());
        console.log("bucket", j, bucket.size(), "peers");
    });
}
<|endoftext|>


<commit_before>/*global require, logger. setInterval, clearInterval, Buffer, exports*/
var Getopt = require('node-getopt');

var spawn = require('child_process').spawn;

var config = require('./../../licode_config');

// Configuration default values
GLOBAL.config = config || {};
GLOBAL.config.erizoAgent = GLOBAL.config.erizoAgent || {};
GLOBAL.config.erizoAgent.maxProcesses = GLOBAL.config.erizoAgent.maxProcesses || 1;
GLOBAL.config.erizoAgent.prerunProcesses = GLOBAL.config.erizoAgent.prerunProcesses || 1;

// Parse command line arguments
var getopt = new Getopt([
  ['r' , 'rabbit-host=ARG'            , 'RabbitMQ Host'],
  ['g' , 'rabbit-port=ARG'            , 'RabbitMQ Port'],
  ['l' , 'logging-config-file=ARG'    , 'Logging Config File'],
  ['M' , 'maxProcesses=ARG'          , 'Stun Server URL'],
  ['P' , 'prerunProcesses=ARG'         , 'Default video Bandwidth'],
  ['h' , 'help'                       , 'display this help']
]);

opt = getopt.parse(process.argv.slice(2));

for (var prop in opt.options) {
    if (opt.options.hasOwnProperty(prop)) {
        var value = opt.options[prop];
        switch (prop) {
            case "help":
                getopt.showHelp();
                process.exit(0);
                break;
            case "rabbit-host":
                GLOBAL.config.rabbit = GLOBAL.config.rabbit || {};
                GLOBAL.config.rabbit.host = value;
                break;
            case "rabbit-port":
                GLOBAL.config.rabbit = GLOBAL.config.rabbit || {};
                GLOBAL.config.rabbit.port = value;
                break;
            case "logging-config-file":
                GLOBAL.config.logger = GLOBAL.config.logger || {};
                GLOBAL.config.logger.config_file = value;
                break;
            default:
                GLOBAL.config.erizoAgent[prop] = value;
                break;
        }
    }
}

// Load submodules with updated config
var logger = require('./../common/logger').logger;
var amqper = require('./../common/amqper');

// Logger
var log = logger.getLogger("ErizoAgent");

var childs = [];

var SEARCH_INTERVAL = 5000;

var idle_erizos = [];

var erizos = [];

var processes = {};

var guid = (function() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
               .toString(16)
               .substring(1);
  }
  return function() {
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
           s4() + '-' + s4() + s4() + s4();
  };
})();

var saveChild = function(id) {
    childs.push(id);
};

var removeChild = function(id) {
    childs.push(id);
};

var launchErizoJS = function() {
    console.log("Running process");
    var id = guid();
    var fs = require('fs');
    var out = fs.openSync('./erizo-' + id + '.log', 'a');
    var err = fs.openSync('./erizo-' + id + '.log', 'a');
    var erizoProcess = spawn('./launch.sh', ['./../erizoJS/erizoJS.js', id], { detached: true, stdio: [ 'ignore', out, err ] });
    erizoProcess.unref();
    erizoProcess.on('close', function (code) {
        var index = idle_erizos.indexOf(id);
        var index2 = erizos.indexOf(id);
        if (index > -1) {
            idle_erizos.splice(index, 1);
            launchErizoJS();
        } else if (index2 > -1) {
            erizos.splice(index2, 1);
        }
        delete processes[id];
        fillErizos();
    });
    processes[id] = erizoProcess;
    idle_erizos.push(id);
};

var dropErizoJS = function(erizo_id, callback) {
   if (processes.hasOwnProperty(erizo_id)) {
      var process = processes[erizo_id];
      process.kill();
      delete processes[erizo_id];
      callback("callback", "ok");
   }
};

var fillErizos = function() {
    for (var i = idle_erizos.length; i<GLOBAL.config.erizoAgent.prerunProcesses; i++) {
        launchErizoJS();
    }
};

var api = {
    createErizoJS: function(callback) {
        try {
            var erizo_id = idle_erizos.pop();
            callback("callback", erizo_id);

            // We re-use Erizos
            if ((erizos.length + idle_erizos.length + 1) >= GLOBAL.config.erizoAgent.maxProcesses) {
                idle_erizos.push(erizo_id);
            } else {
                erizos.push(erizo_id);
            }

            // We launch more processes
            fillErizos();

        } catch (error) {
            console.log("Error in ErizoAgent:", error);
        }
    },
    deleteErizoJS: function(id, callback) {
        try {
            dropErizoJS(id, callback);
        } catch(err) {
            log.error("Error stopping ErizoJS");
        }
    }
};

fillErizos();

amqper.connect(function () {
    "use strict";
    amqper.setPublicRPC(api);

    var rpcID = "ErizoAgent";

    amqper.bind(rpcID);

});

/*
setInterval(function() {
    var search = spawn("ps", ['-aef']);

    search.stdout.on('data', function(data) {

    });

    search.on('close', function (code) {

    });
}, SEARCH_INTERVAL);
*/
<commit_msg>Improvements in erizojs assignements<commit_after>/*global require, logger. setInterval, clearInterval, Buffer, exports*/
var Getopt = require('node-getopt');

var spawn = require('child_process').spawn;

var config = require('./../../licode_config');

// Configuration default values
GLOBAL.config = config || {};
GLOBAL.config.erizoAgent = GLOBAL.config.erizoAgent || {};
GLOBAL.config.erizoAgent.maxProcesses = GLOBAL.config.erizoAgent.maxProcesses || 1;
GLOBAL.config.erizoAgent.prerunProcesses = GLOBAL.config.erizoAgent.prerunProcesses === undefined ? 1 : GLOBAL.config.erizoAgent.prerunProcesses;

// Parse command line arguments
var getopt = new Getopt([
  ['r' , 'rabbit-host=ARG'            , 'RabbitMQ Host'],
  ['g' , 'rabbit-port=ARG'            , 'RabbitMQ Port'],
  ['l' , 'logging-config-file=ARG'    , 'Logging Config File'],
  ['M' , 'maxProcesses=ARG'          , 'Stun Server URL'],
  ['P' , 'prerunProcesses=ARG'         , 'Default video Bandwidth'],
  ['h' , 'help'                       , 'display this help']
]);

opt = getopt.parse(process.argv.slice(2));

for (var prop in opt.options) {
    if (opt.options.hasOwnProperty(prop)) {
        var value = opt.options[prop];
        switch (prop) {
            case "help":
                getopt.showHelp();
                process.exit(0);
                break;
            case "rabbit-host":
                GLOBAL.config.rabbit = GLOBAL.config.rabbit || {};
                GLOBAL.config.rabbit.host = value;
                break;
            case "rabbit-port":
                GLOBAL.config.rabbit = GLOBAL.config.rabbit || {};
                GLOBAL.config.rabbit.port = value;
                break;
            case "logging-config-file":
                GLOBAL.config.logger = GLOBAL.config.logger || {};
                GLOBAL.config.logger.config_file = value;
                break;
            default:
                GLOBAL.config.erizoAgent[prop] = value;
                break;
        }
    }
}

// Load submodules with updated config
var logger = require('./../common/logger').logger;
var amqper = require('./../common/amqper');

// Logger
var log = logger.getLogger("ErizoAgent");

var childs = [];

var SEARCH_INTERVAL = 5000;

var idle_erizos = [];

var erizos = [];

var processes = {};

var guid = (function() {
  function s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
               .toString(16)
               .substring(1);
  }
  return function() {
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
           s4() + '-' + s4() + s4() + s4();
  };
})();

var saveChild = function(id) {
    childs.push(id);
};

var removeChild = function(id) {
    childs.push(id);
};

var launchErizoJS = function() {
    console.log("Running process");
    var id = guid();
    var fs = require('fs');
    var out = fs.openSync('./erizo-' + id + '.log', 'a');
    var err = fs.openSync('./erizo-' + id + '.log', 'a');
    var erizoProcess = spawn('./launch.sh', ['./../erizoJS/erizoJS.js', id], { detached: true, stdio: [ 'ignore', out, err ] });
    erizoProcess.unref();
    erizoProcess.on('close', function (code) {

        var index = idle_erizos.indexOf(id);
        var index2 = erizos.indexOf(id);
        if (index > -1) {
            idle_erizos.splice(index, 1);
        } else if (index2 > -1) {
            erizos.splice(index2, 1);
        }
        delete processes[id];
        fillErizos();
    });

    log.info('Launched new ErizoJS ', id);
    processes[id] = erizoProcess;
    idle_erizos.push(id);
};

var dropErizoJS = function(erizo_id, callback) {
   if (processes.hasOwnProperty(erizo_id)) {
      var process = processes[erizo_id];
      process.kill();
      delete processes[erizo_id];
      callback("callback", "ok");
   }
};

var fillErizos = function () {
    if (erizos.length + idle_erizos.length < GLOBAL.config.erizoAgent.maxProcesses) {
        if (idle_erizos.length < GLOBAL.config.erizoAgent.prerunProcesses) {
            launchErizoJS();
            fillErizos();
        }
    }
};

var getErizo = function () {

    var erizo_id = idle_erizos.shift();

    if (!erizo_id) {
        if (erizos.length < GLOBAL.config.erizoAgent.maxProcesses) {
            launchErizoJS();
            return getErizo();
        } else {
            erizo_id = erizos.shift();
        }
    }

    return erizo_id;
}

var api = {
    createErizoJS: function(callback) {
        try {

            var erizo_id = getErizo();

            callback("callback", erizo_id);

            erizos.push(erizo_id);
            fillErizos();

        } catch (error) {
            console.log("Error in ErizoAgent:", error);
        }
    },
    deleteErizoJS: function(id, callback) {
        try {
            dropErizoJS(id, callback);
        } catch(err) {
            log.error("Error stopping ErizoJS");
        }
    }
};

fillErizos();

amqper.connect(function () {
    "use strict";
    amqper.setPublicRPC(api);

    var rpcID = "ErizoAgent";

    amqper.bind(rpcID);

});

/*
setInterval(function() {
    var search = spawn("ps", ['-aef']);

    search.stdout.on('data', function(data) {

    });

    search.on('close', function (code) {

    });
}, SEARCH_INTERVAL);
*/
<|endoftext|>