<commit_before>
"use strict";

var User = require('./User');
var Channel = require('./Channel');

var EventEmitter = require('events').EventEmitter;

var MumbleWrapper = function(connection) {
    this._users = {}; //Collect high-level information about users
    this._channels = {}; //Collect high-level information about channels
    this.sessions = {}; //Sessions to user map

    //TODO: We are storing the users twice to avoid some looping. Is this the
    //best approach?
    this.ready = false;
    this.connection = connection;
    this.rootChannel = undefined;
    this.user = undefined;

    // Generate list of free whisper target IDs.
    this.freeTargets = [];
    for( var i = 1; i <= 30; i++ ) {
        this.freeTargets.push( i );
    }

    /*
     * Forward all events from the connection
     */
    this.on('newListener', function (event, listener) {
        connection.on(event, listener);
    });

    connection.on('serverSync', this._serverSync.bind( this ) );
    connection.on('userState', this._userState.bind( this ) );
    connection.on('channelState', this._channelState.bind( this ) );
    connection.on('textMessage', this._textMessage.bind( this ) );
};

MumbleWrapper.prototype = Object.create( EventEmitter.prototype );

MumbleWrapper.prototype._serverSync = function(data) {
    this.session = data.session;
    this.maxBandwidth = data.maxBandwidth;
    this.user = this.sessions[this.session];
    this.ready = true;
    this.emit('ready');
    //Is really everything ready when we receive this?
    //TODO: Is this a good idea?
};

MumbleWrapper.prototype._textMessage = function(data) {
    var actor = this.sessions[data.actor];
    if(actor !== undefined) {
        if(data.session !== undefined) { // Then it was a private text message
            this.emit('message', data.message, actor, 'private');
        }
        else if(data.channelId !== undefined) { // A message to the channel
            this.emit('message', data.message, actor, 'channel');
        }
    }
};

MumbleWrapper.prototype._newUser = function(data) {
    var user = new User(data, this);

    this._wrapEvents( user, 'user', [
        'move', 'mute', 'self-mute', 'self-deaf', 'suppress',
        'recording', 'priority-speaker' ] );

    if(this.ready) {
        this.emit('user-connected', user);
    }
    return user;
};

MumbleWrapper.prototype._newChannel = function(data) {
    var channel = new Channel(data, this);
    if(channel.id === 0) {
        this.rootChannel = channel;
    }

    this._wrapEvents( channel, 'channel', [
        'rename', 'links-add', 'links-remove', 'move' ] );

    if(this.ready) {
        this.emit('channel-created', channel);
    }
    return channel;
};

/**
 * Wraps source events to be emitted from the Wrapper as well.
 *
 * @param {Object} source - Original event source
 * @param {String} prefix - Event prefix for the events the source emits.
 * @param {String[]} events - Events to delegate.
 */
MumbleWrapper.prototype._wrapEvents = function( source, prefix, events ) {
    var self = this;

    // Process all events
    events.forEach( function( event ) {
        var myEvent = prefix + '-' + event;
        source.on( event, function() {

            // Construct the event arguments for the 'emit' call:
            // eventName, source, ...original arguments
            var args = Array.prototype.slice.call( arguments );
            args.unshift( source );
            args.unshift( myEvent );

            // Emit.
            self.emit.apply( self, args );
        });
    });
};

MumbleWrapper.prototype._channelState = function(data) {
    if(this._channels[data.channelId] === undefined) {
        this._channels[data.channelId] = this._newChannel(data);
    }
    else {
        var channel = this._channels[data.channelId];
        channel.update(data);
    }
};

MumbleWrapper.prototype._userState = function(data) {
    if(this.sessions[data.session] === undefined) {
        var user = this._newUser(data);
        this._users[data.userId] = user;
        this.sessions[data.session] = user;
    }
    else {
        this.sessions[data.session].update( data );
    }
};

MumbleWrapper.prototype.users = function() {
    var list = [];
    for(var key in this._users) {
        list.push(this._users[key]);
    }
    return list;
};

MumbleWrapper.prototype.channelById = function(id) {
    return this._channels[id];
};

MumbleWrapper.prototype.userById = function(id) {
    return this._users[id];
};

MumbleWrapper.prototype.channelByName = function(name) {
    for(var key in this._channels) {
        var channel = this._channels[key];
        if(channel.name === name) {
            return channel;
        }
    }
    //Else return undefined
};

MumbleWrapper.prototype.userByName = function(name) {
    for(var key in this._users) {
        var user = this._users[key];
        if(user.name === name) {
            return user;
        }
    }
    //Else return undefined
};

/**
 * Allocates an input stream for a specific user session
 *
 * @param {Number|Array} sessionId - Single user session ID or an array of those.
 */
MumbleWrapper.prototype.inputStreamForUser = function( sessionId ) {
    var self = this;

    // If we got no free target IDs left, return null.
    if( this.freeTargets.length === 0 ) {
        return null;
    }

    // Create the targets parameter based on the sessionId type.
    var targets = [];
    if( typeof sessionId === 'number' ) {
        targets.push({ session: [ sessionId ] });
    } else {
        targets.push({ session: sessionId });
    }

    var whisperId = this.freeTargets.shift();

    this.sendMessage( 'VoiceTarget', {
        targets: targets,
        id: whisperId
    });

    var inputStream = this.connection.inputStream( whisperId );

    // Return the whisper ID to the target pool when we close the stream.
    inputStream.on( 'finish', function() {
        self.freeTargets.push( whisperId );
    });

    return inputStream;
};

/*
 * Forward all relevant methods from the connection
 */
MumbleWrapper.prototype.authenticate = function(name, password) {
    return this.connection.authenticate(name, password); };
MumbleWrapper.prototype.sendMessage = function(type, data) {
    return this.connection.sendMessage(type, data); };
MumbleWrapper.prototype.outputStream = function(userid) {
    return this.connection.outputStream(userid); };
MumbleWrapper.prototype.inputStream = function() {
    return this.connection.inputStream(); };
MumbleWrapper.prototype.joinPath = function(path) {
    return this.connection.joinPath(path); };
MumbleWrapper.prototype.sendVoice = function(chunk) {
    return this.connection.sendVoice(chunk); };
MumbleWrapper.prototype.disconnect = function() {
    return this.connection.disconnect(); };

module.exports = MumbleWrapper;
<commit_msg>Attempt to fix ready event when not really ready.<commit_after>
"use strict";

var User = require('./User');
var Channel = require('./Channel');

var EventEmitter = require('events').EventEmitter;

var MumbleWrapper = function(connection) {
    this._users = {}; //Collect high-level information about users
    this._channels = {}; //Collect high-level information about channels
    this.sessions = {}; //Sessions to user map

    //TODO: We are storing the users twice to avoid some looping. Is this the
    //best approach?
    this.ready = false;
    this.connection = connection;
    this.rootChannel = undefined;
    this.user = undefined;
    this._gotServerSync;
    this._gotPermissionQuery;

    // Generate list of free whisper target IDs.
    this.freeTargets = [];
    for( var i = 1; i <= 30; i++ ) {
        this.freeTargets.push( i );
    }

    /*
     * Forward all events from the connection
     */
    this.on('newListener', function (event, listener) {
        connection.on(event, listener);
    });

    connection.on('permissionQuery', this._permissionQuery.bind( this ) );
    connection.on('serverSync', this._serverSync.bind( this ) );
    connection.on('userState', this._userState.bind( this ) );
    connection.on('channelState', this._channelState.bind( this ) );
    connection.on('textMessage', this._textMessage.bind( this ) );
};

MumbleWrapper.prototype = Object.create( EventEmitter.prototype );

MumbleWrapper.prototype._checkReady = function() {
    if(this._gotPermissionQuery && this._gotServerSync) {
        this.ready = true;
        this.emit('ready');
    }
};

MumbleWrapper.prototype._permissionQuery = function(data) {
    //TODO: Do something with the data
    this._gotPermissionQuery = true;
    this._checkReady();
};

MumbleWrapper.prototype._serverSync = function(data) {
    this.session = data.session;
    this.maxBandwidth = data.maxBandwidth;
    this.user = this.sessions[this.session];
    this._gotServerSync = true;
    this._checkReady();
    //Is really everything ready when we receive this?
    //TODO: Is this a good idea?
};

MumbleWrapper.prototype._textMessage = function(data) {
    var actor = this.sessions[data.actor];
    if(actor !== undefined) {
        if(data.session !== undefined) { // Then it was a private text message
            this.emit('message', data.message, actor, 'private');
        }
        else if(data.channelId !== undefined) { // A message to the channel
            this.emit('message', data.message, actor, 'channel');
        }
    }
};

MumbleWrapper.prototype._newUser = function(data) {
    var user = new User(data, this);

    this._wrapEvents( user, 'user', [
        'move', 'mute', 'self-mute', 'self-deaf', 'suppress',
        'recording', 'priority-speaker' ] );

    if(this.ready) {
        this.emit('user-connected', user);
    }
    return user;
};

MumbleWrapper.prototype._newChannel = function(data) {
    var channel = new Channel(data, this);
    if(channel.id === 0) {
        this.rootChannel = channel;
    }

    this._wrapEvents( channel, 'channel', [
        'rename', 'links-add', 'links-remove', 'move' ] );

    if(this.ready) {
        this.emit('channel-created', channel);
    }
    return channel;
};

/**
 * Wraps source events to be emitted from the Wrapper as well.
 *
 * @param {Object} source - Original event source
 * @param {String} prefix - Event prefix for the events the source emits.
 * @param {String[]} events - Events to delegate.
 */
MumbleWrapper.prototype._wrapEvents = function( source, prefix, events ) {
    var self = this;

    // Process all events
    events.forEach( function( event ) {
        var myEvent = prefix + '-' + event;
        source.on( event, function() {

            // Construct the event arguments for the 'emit' call:
            // eventName, source, ...original arguments
            var args = Array.prototype.slice.call( arguments );
            args.unshift( source );
            args.unshift( myEvent );

            // Emit.
            self.emit.apply( self, args );
        });
    });
};

MumbleWrapper.prototype._channelState = function(data) {
    if(this._channels[data.channelId] === undefined) {
        this._channels[data.channelId] = this._newChannel(data);
    }
    else {
        var channel = this._channels[data.channelId];
        channel.update(data);
    }
};

MumbleWrapper.prototype._userState = function(data) {
    if(this.sessions[data.session] === undefined) {
        var user = this._newUser(data);
        this._users[data.userId] = user;
        this.sessions[data.session] = user;
    }
    else {
        this.sessions[data.session].update( data );
    }
};

MumbleWrapper.prototype.users = function() {
    var list = [];
    for(var key in this._users) {
        list.push(this._users[key]);
    }
    return list;
};

MumbleWrapper.prototype.channelById = function(id) {
    return this._channels[id];
};

MumbleWrapper.prototype.userById = function(id) {
    return this._users[id];
};

MumbleWrapper.prototype.channelByName = function(name) {
    for(var key in this._channels) {
        var channel = this._channels[key];
        if(channel.name === name) {
            return channel;
        }
    }
    //Else return undefined
};

MumbleWrapper.prototype.userByName = function(name) {
    for(var key in this._users) {
        var user = this._users[key];
        if(user.name === name) {
            return user;
        }
    }
    //Else return undefined
};

/**
 * Allocates an input stream for a specific user session
 *
 * @param {Number|Array} sessionId - Single user session ID or an array of those.
 */
MumbleWrapper.prototype.inputStreamForUser = function( sessionId ) {
    var self = this;

    // If we got no free target IDs left, return null.
    if( this.freeTargets.length === 0 ) {
        return null;
    }

    // Create the targets parameter based on the sessionId type.
    var targets = [];
    if( typeof sessionId === 'number' ) {
        targets.push({ session: [ sessionId ] });
    } else {
        targets.push({ session: sessionId });
    }

    var whisperId = this.freeTargets.shift();

    this.sendMessage( 'VoiceTarget', {
        targets: targets,
        id: whisperId
    });

    var inputStream = this.connection.inputStream( whisperId );

    // Return the whisper ID to the target pool when we close the stream.
    inputStream.on( 'finish', function() {
        self.freeTargets.push( whisperId );
    });

    return inputStream;
};

/*
 * Forward all relevant methods from the connection
 */
MumbleWrapper.prototype.authenticate = function(name, password) {
    return this.connection.authenticate(name, password); };
MumbleWrapper.prototype.sendMessage = function(type, data) {
    return this.connection.sendMessage(type, data); };
MumbleWrapper.prototype.outputStream = function(userid) {
    return this.connection.outputStream(userid); };
MumbleWrapper.prototype.inputStream = function() {
    return this.connection.inputStream(); };
MumbleWrapper.prototype.joinPath = function(path) {
    return this.connection.joinPath(path); };
MumbleWrapper.prototype.sendVoice = function(chunk) {
    return this.connection.sendVoice(chunk); };
MumbleWrapper.prototype.disconnect = function() {
    return this.connection.disconnect(); };

module.exports = MumbleWrapper;
<|endoftext|>


<commit_before>module.exports = function(grunt) {
  var path = require('path');

  grunt.loadNpmTasks('grunt-karma');

  grunt.config('karma',{
    options: {
            basePath: '../',

            // web server port
            port: 9876,

            // browserNoActivityTimeout: 100000,

            client: {
              captureConsole: !!grunt.option("with-logs")
            },

            // enable / disable watching file and executing tests whenever any file changes
            autoWatch: true,

            browsers: ['PhantomJS'],

            exclude: [],

            files: [
              'Angular-Grunt-Boilerplate/target/build/webapp/app.js',
              'Angular-Grunt-Boilerplate/tests/spec/test.js',
            ],

            plugins : [
                'karma-jasmine',
                'karma-phantomjs-launcher',
            ],

            // frameworks to use
            // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
            // requirejs issue, "Disconnected (1 times), because no message in 10000 ms",see below:
            // http://stackoverflow.com/questions/23667102/disconnected-1-times-because-no-message-in-10000-ms-using-karma-jasmine
            frameworks: ['jasmine'],

            // test results reporter to use
            // possible values: 'dots', 'progress'
            // available reporters: https://npmjs.org/browse/keyword/karma-reporter
            // html - https://github.com/matthias-schuetz/karma-htmlfile-reporter
            reporters: ['progress'],

            colors: true,

    },

    daemon: {
        options: {
            singleRun: false
        }
    },

    run: {
        options: {
          singleRun: true
        }
    }
  });
};<commit_msg>Adjustment.<commit_after>module.exports = function(grunt) {
  var path = require('path');

  grunt.loadNpmTasks('grunt-karma');

  grunt.config('karma',{
    options: {
            basePath: './',

            // web server port
            port: 9876,

            // browserNoActivityTimeout: 100000,

            client: {
              captureConsole: !!grunt.option("with-logs")
            },

            // enable / disable watching file and executing tests whenever any file changes
            autoWatch: true,

            browsers: ['PhantomJS'],

            exclude: [],

            files: [
              'target/build/webapp/app.js',
              'tests/spec/test.js',
            ],

            plugins : [
                'karma-jasmine',
                'karma-phantomjs-launcher',
            ],

            // frameworks to use
            // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
            // requirejs issue, "Disconnected (1 times), because no message in 10000 ms",see below:
            // http://stackoverflow.com/questions/23667102/disconnected-1-times-because-no-message-in-10000-ms-using-karma-jasmine
            frameworks: ['jasmine'],

            // test results reporter to use
            // possible values: 'dots', 'progress'
            // available reporters: https://npmjs.org/browse/keyword/karma-reporter
            // html - https://github.com/matthias-schuetz/karma-htmlfile-reporter
            reporters: ['progress'],

            colors: true,

    },

    daemon: {
        options: {
            singleRun: false
        }
    },

    run: {
        options: {
          singleRun: true
        }
    }
  });
};<|endoftext|>


<commit_before>'use strict'

var util = require('util')
var CachedPersistence = require('aedes-cached-persistence')
var Packet = CachedPersistence.Packet
var mongojs = require('mongojs')
var pump = require('pump')
var through = require('through2')
var Qlobber = require('qlobber').Qlobber
var qlobberOpts = {
  separator: '/',
  wildcard_one: '+',
  wildcard_some: '#'
}

function MongoPersistence (opts) {
  if (!(this instanceof MongoPersistence)) {
    return new MongoPersistence(opts)
  }

  this._db = mongojs(opts.url, [
    'retained',
    'subscriptions',
    'outgoing',
    'incoming',
    'will'
  ])

  CachedPersistence.call(this, opts)
}

util.inherits(MongoPersistence, CachedPersistence)

MongoPersistence.prototype._setup = function () {
  this.emit('ready')
}

MongoPersistence.prototype.storeRetained = function (packet, cb) {
  var criteria = { topic: packet.topic }
  if (packet.payload.length > 0) {
    this._db.retained.update(criteria, packet, { upsert: true }, cb)
  } else {
    this._db.retained.remove(criteria, cb)
  }
}

function filterStream (pattern) {
  var instance = through.obj(filterPattern)
  instance.matcher = new Qlobber(qlobberOpts)
  instance.matcher.add(pattern, true)
  return instance
}

function filterPattern (chunk, enc, cb) {
  if (this.matcher.match(chunk.topic).length > 0) {
    chunk.payload = chunk.payload.buffer
    // this is converting chunk to slow properties
    // https://github.com/sindresorhus/to-fast-properties
    // might make this faster
    delete chunk._id
    this.push(chunk)
  }
  cb()
}

MongoPersistence.prototype.createRetainedStream = function (pattern) {
  return pump(
    this._db.retained.find({
      topic: new RegExp(pattern.replace(/(#|\+).*$/, ''))
    }),
    filterStream(pattern)
  )
}

MongoPersistence.prototype.addSubscriptions = function (client, subs, cb) {
  var published = 0
  var count = 0
  var errored = false
  var that = this
  var bulk = this._db.subscriptions.initializeOrderedBulkOp()
  subs
    .forEach(function (sub) {
      if (sub.qos > 0) {
        count++
        that._waitFor(client, sub.topic, finish)
      }
      bulk.find({
        clientId: client.id,
        topic: sub.topic
      }).upsert().updateOne({
        clientId: client.id,
        topic: sub.topic,
        qos: sub.qos
      })
    })

  bulk.execute(finish)

  this._addedSubscriptions(client, subs, finish)

  function finish (err) {
    if (err && !errored) {
      errored = true
      cb(err, client)
      return
    } else if (errored) {
      return
    }
    published++
    console.log(published, count)
    if (published === count + 2) {
      cb(null, client)
    }
  }
}

function toSub (topic) {
  return {
    topic: topic
  }
}

MongoPersistence.prototype.removeSubscriptions = function (client, subs, cb) {
  var published = 0
  var count = 0
  var errored = false
  var that = this
  var bulk = this._db.subscriptions.initializeOrderedBulkOp()
  subs
    .forEach(function (topic) {
      count++
      that._waitFor(client, topic, finish)
      bulk.find({
        clientId: client.id,
        topic: topic
      }).removeOne()
    })

  bulk.execute(finish)
  this._removedSubscriptions(client, subs.map(toSub), finish)

  function finish (err) {
    if (err && !errored) {
      errored = true
      cb(err, client)
      return
    }
    published++
    if (published === count + 2 && !errored) {
      cb(null, client)
    }
  }
}

MongoPersistence.prototype.subscriptionsByClient = function (client, cb) {
  this._db.subscriptions.find({ clientId: client.id }).toArray(function (err, subs) {
    if (err) {
      cb(err)
      return
    }

    var toReturn = subs.map(function (sub) {
      return {
        topic: sub.topic,
        qos: sub.qos
      }
    })

    cb(null, toReturn.length > 0 ? toReturn : null, client)
  })
}

MongoPersistence.prototype.countOffline = function (cb) {
  setImmediate(cb, null, 0, 0)
  // var subsCount = -1
  // var clientsCount = -1
  // this._db.subscriptions.aggregate([{
  //   $group: {
  //     _id: '$clientId',
  //     count: {
  //       $sum: 1
  //     }
  //   }
  // }, {
  //   $group: {
  //     count: {
  //       $sum: 1
  //     }
  //   }
  // }]).toArray(function (err, count) {
  //   if (err) { return cb(err) }
  //   console.log(count)
  //   subsCount = parseInt(count)
  //   if (clientsCount >= 0)
  //     cb(null, subsCount, clientsCount)
  // })
  // pipeline.get(offlineClientsCountKey, function (err, count) {
  //   if (err) { return cb(err) }
  //   clientsCount = parseInt(count)
  //   if (subsCount >= 0)
  //     cb(null, subsCount, clientsCount)
  // })
}

MongoPersistence.prototype.destroy = function (cb) {
  cb = cb || noop
  if (!this._db) {
    cb()
    return
  }

  this._db.close(cb)
}

MongoPersistence.prototype.outgoingEnqueue = function (sub, packet, cb) {
  this._db.outgoing.insert({
    clientId: sub.clientId,
    packet: new Packet(packet)
  }, cb)
}

function asPacket (obj, enc, cb) {
  var packet = obj.packet
  if (packet.payload) {
    packet.payload = packet.payload.buffer
  }
  cb(null, packet)
}

MongoPersistence.prototype.outgoingStream = function (client) {
  return pump(
    this._db.outgoing.find({ clientId: client.id }),
    through.obj(asPacket))
}

function updateWithMessageId (db, client, packet, cb) {
  db.outgoing.update({
    clientId: client.id,
    "packet.brokerCounter": packet.brokerCounter,
    "packet.brokerId": packet.brokerId
  }, {
    $set: {
      "packet.messageId": packet.messageId
    }
  }, function (err) {
    cb(err, client, packet)
  })
}

function updatePacket (db, client, packet, cb) {
  db.outgoing.update({
    clientId: client.id,
    "packet.messageId": packet.messageId
  }, {
    clientId: client.id,
    packet: packet
  }, function (err) {
    cb(err, client, packet)
  })
}

MongoPersistence.prototype.outgoingUpdate = function (client, packet, cb) {
  var that = this
  if (packet.brokerId) {
    updateWithMessageId(this._db, client, packet, cb)
  } else {
    updatePacket(this._db, client, packet, cb)
  }
}

MongoPersistence.prototype.outgoingClearMessageId = function (client, packet, cb) {
  this._db.outgoing.remove({
    clientId: client.id,
    "packet.messageId": packet.messageId
  }, function (err) {
    cb(err, client)
  })
}

MongoPersistence.prototype.incomingStorePacket = function (client, packet, cb) {
  var newp = new Packet(packet)
  newp.messageId = packet.messageId

  this._db.incoming.insert({
    clientId: client.id,
    packet: newp
  }, cb)
}

MongoPersistence.prototype.incomingGetPacket = function (client, packet, cb) {
  this._db.incoming.findOne({
    clientId: client.id,
    'packet.messageId': packet.messageId
  }, function (err, result) {
    if (err) {
      cb(err)
      return
    }

    if (!result) {
      cb(new Error('packet not found'), null, client)
      return
    }

    var packet = result.packet

    if (packet && packet.payload) {
      packet.payload = packet.payload.buffer
    }

    cb(null, packet, client)
  })
}

MongoPersistence.prototype.incomingDelPacket = function (client, packet, cb) {
  this._db.incoming.remove({
    clientId: client.id,
    'packet.messageId': packet.messageId
  }, cb)
}

MongoPersistence.prototype.putWill = function (client, packet, cb) {
  packet.clientId = client.id
  packet.brokerId = this.broker.id
  this._db.will.insert({
    clientId: client.id,
    packet: packet
  }, function (err) {
    cb(err, client)
  })
}

MongoPersistence.prototype.getWill = function (client, cb) {
  this._db.will.findOne({
    clientId: client.id
  }, function (err, result) {
    if (err) {
      cb(err)
      return
    }

    if (!result) {
      cb(null, null, client)
      return
    }

    var packet = result.packet

    if (packet && packet.payload) {
      packet.payload = packet.payload.buffer
    }

    cb(null, packet, client)
  })
}

MongoPersistence.prototype.delWill = function (client, cb) {
  var db = this._db
  this.getWill(client, function (err, packet) {
    if (err || !packet) {
      cb(err, null, client)
      return
    }
    db.will.remove({
      clientId: client.id
    }, function (err) {
      cb(err, packet, client)
    })
  })
}

MongoPersistence.prototype.streamWill = function (brokers) {
  var query = {}

  if (brokers) {
    query['packet.brokerId'] = { $nin: Object.keys(brokers) }
  }
  return pump(this._db.will.find(query), through.obj(asPacket))
}

function noop () {}

module.exports = MongoPersistence
<commit_msg>Implemented countOffline.<commit_after>'use strict'

var util = require('util')
var CachedPersistence = require('aedes-cached-persistence')
var Packet = CachedPersistence.Packet
var mongojs = require('mongojs')
var pump = require('pump')
var through = require('through2')
var Qlobber = require('qlobber').Qlobber
var qlobberOpts = {
  separator: '/',
  wildcard_one: '+',
  wildcard_some: '#'
}

function MongoPersistence (opts) {
  if (!(this instanceof MongoPersistence)) {
    return new MongoPersistence(opts)
  }

  this._db = mongojs(opts.url, [
    'retained',
    'subscriptions',
    'outgoing',
    'incoming',
    'will'
  ])

  CachedPersistence.call(this, opts)
}

util.inherits(MongoPersistence, CachedPersistence)

MongoPersistence.prototype._setup = function () {
  this.emit('ready')
}

MongoPersistence.prototype.storeRetained = function (packet, cb) {
  var criteria = { topic: packet.topic }
  if (packet.payload.length > 0) {
    this._db.retained.update(criteria, packet, { upsert: true }, cb)
  } else {
    this._db.retained.remove(criteria, cb)
  }
}

function filterStream (pattern) {
  var instance = through.obj(filterPattern)
  instance.matcher = new Qlobber(qlobberOpts)
  instance.matcher.add(pattern, true)
  return instance
}

function filterPattern (chunk, enc, cb) {
  if (this.matcher.match(chunk.topic).length > 0) {
    chunk.payload = chunk.payload.buffer
    // this is converting chunk to slow properties
    // https://github.com/sindresorhus/to-fast-properties
    // might make this faster
    delete chunk._id
    this.push(chunk)
  }
  cb()
}

MongoPersistence.prototype.createRetainedStream = function (pattern) {
  return pump(
    this._db.retained.find({
      topic: new RegExp(pattern.replace(/(#|\+).*$/, ''))
    }),
    filterStream(pattern)
  )
}

MongoPersistence.prototype.addSubscriptions = function (client, subs, cb) {
  var published = 0
  var count = 0
  var errored = false
  var that = this
  var bulk = this._db.subscriptions.initializeOrderedBulkOp()
  subs
    .forEach(function (sub) {
      if (sub.qos > 0) {
        count++
        that._waitFor(client, sub.topic, finish)
      }
      bulk.find({
        clientId: client.id,
        topic: sub.topic
      }).upsert().updateOne({
        clientId: client.id,
        topic: sub.topic,
        qos: sub.qos
      })
    })

  bulk.execute(finish)

  this._addedSubscriptions(client, subs, finish)

  function finish (err) {
    if (err && !errored) {
      errored = true
      cb(err, client)
      return
    } else if (errored) {
      return
    }
    published++
    console.log(published, count)
    if (published === count + 2) {
      cb(null, client)
    }
  }
}

function toSub (topic) {
  return {
    topic: topic
  }
}

MongoPersistence.prototype.removeSubscriptions = function (client, subs, cb) {
  var published = 0
  var count = 0
  var errored = false
  var that = this
  var bulk = this._db.subscriptions.initializeOrderedBulkOp()
  subs
    .forEach(function (topic) {
      count++
      that._waitFor(client, topic, finish)
      bulk.find({
        clientId: client.id,
        topic: topic
      }).removeOne()
    })

  bulk.execute(finish)
  this._removedSubscriptions(client, subs.map(toSub), finish)

  function finish (err) {
    if (err && !errored) {
      errored = true
      cb(err, client)
      return
    }
    published++
    if (published === count + 2 && !errored) {
      cb(null, client)
    }
  }
}

MongoPersistence.prototype.subscriptionsByClient = function (client, cb) {
  this._db.subscriptions.find({ clientId: client.id }).toArray(function (err, subs) {
    if (err) {
      cb(err)
      return
    }

    var toReturn = subs.map(function (sub) {
      return {
        topic: sub.topic,
        qos: sub.qos
      }
    })

    cb(null, toReturn.length > 0 ? toReturn : null, client)
  })
}

MongoPersistence.prototype.countOffline = function (cb) {
  var subsCount = 0
  var clientsCount = 0
  this._db.subscriptions.aggregate([{
    $match: { qos: { $gt: 0 } }
  }, {
    $group: {
      _id: '$clientId',
      count: {
        $sum: 1
      }
    }
  }]).on('data', function (group) {
    clientsCount++
    subsCount += group.count
  }).on('end', function () {
    cb(null, subsCount, clientsCount)
  }).on('error', cb)
}

MongoPersistence.prototype.destroy = function (cb) {
  cb = cb || noop
  if (!this._db) {
    cb()
    return
  }

  this._db.close(cb)
}

MongoPersistence.prototype.outgoingEnqueue = function (sub, packet, cb) {
  this._db.outgoing.insert({
    clientId: sub.clientId,
    packet: new Packet(packet)
  }, cb)
}

function asPacket (obj, enc, cb) {
  var packet = obj.packet
  if (packet.payload) {
    packet.payload = packet.payload.buffer
  }
  cb(null, packet)
}

MongoPersistence.prototype.outgoingStream = function (client) {
  return pump(
    this._db.outgoing.find({ clientId: client.id }),
    through.obj(asPacket))
}

function updateWithMessageId (db, client, packet, cb) {
  db.outgoing.update({
    clientId: client.id,
    "packet.brokerCounter": packet.brokerCounter,
    "packet.brokerId": packet.brokerId
  }, {
    $set: {
      "packet.messageId": packet.messageId
    }
  }, function (err) {
    cb(err, client, packet)
  })
}

function updatePacket (db, client, packet, cb) {
  db.outgoing.update({
    clientId: client.id,
    "packet.messageId": packet.messageId
  }, {
    clientId: client.id,
    packet: packet
  }, function (err) {
    cb(err, client, packet)
  })
}

MongoPersistence.prototype.outgoingUpdate = function (client, packet, cb) {
  var that = this
  if (packet.brokerId) {
    updateWithMessageId(this._db, client, packet, cb)
  } else {
    updatePacket(this._db, client, packet, cb)
  }
}

MongoPersistence.prototype.outgoingClearMessageId = function (client, packet, cb) {
  this._db.outgoing.remove({
    clientId: client.id,
    "packet.messageId": packet.messageId
  }, function (err) {
    cb(err, client)
  })
}

MongoPersistence.prototype.incomingStorePacket = function (client, packet, cb) {
  var newp = new Packet(packet)
  newp.messageId = packet.messageId

  this._db.incoming.insert({
    clientId: client.id,
    packet: newp
  }, cb)
}

MongoPersistence.prototype.incomingGetPacket = function (client, packet, cb) {
  this._db.incoming.findOne({
    clientId: client.id,
    'packet.messageId': packet.messageId
  }, function (err, result) {
    if (err) {
      cb(err)
      return
    }

    if (!result) {
      cb(new Error('packet not found'), null, client)
      return
    }

    var packet = result.packet

    if (packet && packet.payload) {
      packet.payload = packet.payload.buffer
    }

    cb(null, packet, client)
  })
}

MongoPersistence.prototype.incomingDelPacket = function (client, packet, cb) {
  this._db.incoming.remove({
    clientId: client.id,
    'packet.messageId': packet.messageId
  }, cb)
}

MongoPersistence.prototype.putWill = function (client, packet, cb) {
  packet.clientId = client.id
  packet.brokerId = this.broker.id
  this._db.will.insert({
    clientId: client.id,
    packet: packet
  }, function (err) {
    cb(err, client)
  })
}

MongoPersistence.prototype.getWill = function (client, cb) {
  this._db.will.findOne({
    clientId: client.id
  }, function (err, result) {
    if (err) {
      cb(err)
      return
    }

    if (!result) {
      cb(null, null, client)
      return
    }

    var packet = result.packet

    if (packet && packet.payload) {
      packet.payload = packet.payload.buffer
    }

    cb(null, packet, client)
  })
}

MongoPersistence.prototype.delWill = function (client, cb) {
  var db = this._db
  this.getWill(client, function (err, packet) {
    if (err || !packet) {
      cb(err, null, client)
      return
    }
    db.will.remove({
      clientId: client.id
    }, function (err) {
      cb(err, packet, client)
    })
  })
}

MongoPersistence.prototype.streamWill = function (brokers) {
  var query = {}

  if (brokers) {
    query['packet.brokerId'] = { $nin: Object.keys(brokers) }
  }
  return pump(this._db.will.find(query), through.obj(asPacket))
}

function noop () {}

module.exports = MongoPersistence
<|endoftext|>


<commit_before>define([
    './utils/StringUtil',
    './AttrDef',
    './MediaDef',
    './Binding',
    './polyfills/CustomEvent',
    './polyfills/Object.assign',
    '../bower_components/webcomponentsjs/webcomponents-lite.js',
    '../bower_components/matchMedia/matchMedia',
    '../bower_components/matchMedia/matchMedia.addListener'
], function (
    StringUtil,
    AttrDef,
    MediaDef,
    Binding
) {
    'use strict';

    function XElement () {}

    XElement.mixin = {

        customAttributes: [],

        createdCallback: function () {
            this.bindings = [];
            this.mediaDefs = this.customAttributes
                .filter(function (x) { return x.params.responsive === true; })
                .map(function (attrDef) {
                    return new MediaDef({
                        element: this,
                        attrDef: attrDef
                    });
                }, this);
        },

        attachedCallback: function () {
            this.mediaDefs.forEach(function (mediaDef) {
                mediaDef.update();
            });
        },

        detachedCallback: function () {
            this.mediaDefs.forEach(function (mediaDef) {
                mediaDef.update();
            });
        },

        attributeChangedCallback: function (attrName, oldVal, newVal) {
            console.log('attributeChangedCallback');
            var attrDef = this.customAttributes[attrName];
            if (attrDef === undefined) {
                return;
            }
            var oldProp;
            var oldEvaluatedProp;
            var newEvaluatedProp;
            var currentProp = 'current' + StringUtil.capitalize(StringUtil.toCamelCase(attrName));
            if (attrDef.responsive === true && document.contains(this)) {
                oldProp = (oldVal === null) ? '' + attrDef.default : oldVal;
                oldEvaluatedProp = AttrDef.evaluateResponsiveAttribute(oldProp, attrDef.type);
                XElement.updateResponsiveAttribute(this, attrName, attrDef);
                newEvaluatedProp = this[currentProp];
                if (oldEvaluatedProp !== newEvaluatedProp && attrDef.mediaChangedCallback instanceof Function) {
                    attrDef.mediaChangedCallback.call(this, oldEvaluatedProp, newEvaluatedProp);
                }
            }
            if (attrDef.changedCallback !== undefined) {
                attrDef.changedCallback.call(this, oldVal, newVal);
            }
        },

        getComponent: function (T, tag) {
            var selector = T.prototype.selector;
            if (tag !== undefined) {
                selector += '[data-tag="' + tag + '"]';
            }
            return this.querySelector(selector);
        },

        getComponents: function (T, tag) {
            var selector = T.prototype.selector;
            if (tag !== undefined) {
                selector += '[data-tag="' + tag + '"]';
            }
            return this.querySelectorAll(selector);
        },

        findWithTag: function (tag) {
            return this.querySelector('[data-tag="' + tag + '"]');
        },

        findAllWithTag: function (tag) {
            return this.querySelectorAll('[data-tag="' + tag + '"]');
        },

        createBinding: function (target, type, handler) {
            var binding = new Binding(target, type, handler);
            this.bindings.push(binding);
            return binding;
        },

        enable: function () {
            var i;
            var l = this.bindings.length;
            for (i = 0; i < l; i++) {
                this.bindings[i].enable();
            }
        },

        disable: function () {
            var i;
            var l = this.bindings.length;
            for (i = 0; i < l; i++) {
                this.bindings[i].disable();
            }
        },

        trigger: function (type, detail) {
            var e = new CustomEvent(type, { detail: detail });
            return this.dispatchEvent(e);
        }

    };

    XElement.attribute = function (name, params) {
        return new AttrDef(name, params);
    };

    XElement.define = function (customTagName, definition) {
        var base = HTMLElement.prototype;
        var prototype = Object.create(base);
        Object.assign(prototype, XElement.mixin);
        Object.defineProperty(prototype, 'selector', { value: customTagName });
        definition(prototype, XElement.mixin, base);
        return _register(customTagName, { prototype: prototype });
    };

    XElement.extend = function () {
        if (typeof arguments[0] === 'string') {
            return _extendNative.apply(this, arguments);
        }
        return _extendCustom.apply(this, arguments);
    };

    var _extendNative = function (tagName, customTagName, definition) {
        var base = document.createElement(tagName).constructor.prototype;
        var prototype = Object.create(base);
        Object.assign(prototype, XElement.mixin);
        Object.defineProperty(prototype, 'selector', { value: tagName + '[is="' + customTagName + '"]' });
        definition(prototype, XElement.mixin, base);

        return _register(customTagName, { prototype: prototype, extends: tagName });
    };

    var _extendCustom = function (T, customTagName, definition) {
        var options = {};
        var selector;
        var selectorParts = T.prototype.selector.split('[');
        var extendsNative = selectorParts.length > 1;
        if (extendsNative) {
            options.extends = selectorParts[0];
            selector = options.extends + '[is="' + customTagName + '"]';
        } else {
            selector = customTagName;
        }
        var base = T.prototype;
        var prototype = Object.create(base);
        Object.defineProperty(prototype, 'selector', { value: selector });
        definition(prototype, XElement.mixin, base);

        options.prototype = prototype;
        return _register(customTagName, options);
    };

    var _register = function (customTagName, options) {

        // Register custom attributes
        var prototype = options.prototype;
        prototype.customAttributes.forEach(function (attrDef) {
            attrDef.addToPrototype(prototype);
        });

        // Register the custom element
        return document.registerElement(customTagName, options);
    };

    return XElement;
});
<commit_msg>Simplifies the attributeChangedCallback after the MediaDef refactor.<commit_after>define([
    './utils/StringUtil',
    './AttrDef',
    './MediaDef',
    './Binding',
    './polyfills/CustomEvent',
    './polyfills/Object.assign',
    '../bower_components/webcomponentsjs/webcomponents-lite.js',
    '../bower_components/matchMedia/matchMedia',
    '../bower_components/matchMedia/matchMedia.addListener'
], function (
    StringUtil,
    AttrDef,
    MediaDef,
    Binding
) {
    'use strict';

    function XElement () {}

    XElement.mixin = {

        customAttributes: [],

        createdCallback: function () {
            this.bindings = [];
            this.mediaDefs = this.customAttributes
                .filter(function (x) { return x.params.responsive === true; })
                .map(function (attrDef) {
                    return new MediaDef({
                        element: this,
                        attrDef: attrDef
                    });
                }, this);
        },

        attachedCallback: function () {
            this.mediaDefs.forEach(function (mediaDef) {
                mediaDef.update();
            });
        },

        detachedCallback: function () {
            this.mediaDefs.forEach(function (mediaDef) {
                mediaDef.update();
            });
        },

        attributeChangedCallback: function (attrName, oldVal, newVal) {
            var attrDef = this.customAttributes.find(function (x) { return x.name === attrName; });
            if (attrDef === undefined) {
                return;
            }
            attrDef.params.changedCallback.call(this, oldVal, newVal);

            var mediaDef = this.mediaDefs.find(function (x) { return x.params.attrDef === attrDef; });
            if (mediaDef === undefined || document.contains(this) === false) {
                return;
            }

            mediaDef.update();

            var oldProp = (oldVal === null) ? '' + attrDef.params.default : oldVal;
            var oldEvaluatedProp = attrDef.evaluateResponsiveAttribute(oldProp);
            var newEvaluatedProp = this[attrDef.getEvaluatedPropertyName()];
            if (oldEvaluatedProp !== newEvaluatedProp) {
                attrDef.params.mediaChangedCallback.call(this, oldEvaluatedProp, newEvaluatedProp);
            }
        },

        getComponent: function (T, tag) {
            var selector = T.prototype.selector;
            if (tag !== undefined) {
                selector += '[data-tag="' + tag + '"]';
            }
            return this.querySelector(selector);
        },

        getComponents: function (T, tag) {
            var selector = T.prototype.selector;
            if (tag !== undefined) {
                selector += '[data-tag="' + tag + '"]';
            }
            return this.querySelectorAll(selector);
        },

        findWithTag: function (tag) {
            return this.querySelector('[data-tag="' + tag + '"]');
        },

        findAllWithTag: function (tag) {
            return this.querySelectorAll('[data-tag="' + tag + '"]');
        },

        createBinding: function (target, type, handler) {
            var binding = new Binding(target, type, handler);
            this.bindings.push(binding);
            return binding;
        },

        enable: function () {
            var i;
            var l = this.bindings.length;
            for (i = 0; i < l; i++) {
                this.bindings[i].enable();
            }
        },

        disable: function () {
            var i;
            var l = this.bindings.length;
            for (i = 0; i < l; i++) {
                this.bindings[i].disable();
            }
        },

        trigger: function (type, detail) {
            var e = new CustomEvent(type, { detail: detail });
            return this.dispatchEvent(e);
        }

    };

    XElement.attribute = function (name, params) {
        return new AttrDef(name, params);
    };

    XElement.define = function (customTagName, definition) {
        var base = HTMLElement.prototype;
        var prototype = Object.create(base);
        Object.assign(prototype, XElement.mixin);
        Object.defineProperty(prototype, 'selector', { value: customTagName });
        definition(prototype, XElement.mixin, base);
        return _register(customTagName, { prototype: prototype });
    };

    XElement.extend = function () {
        if (typeof arguments[0] === 'string') {
            return _extendNative.apply(this, arguments);
        }
        return _extendCustom.apply(this, arguments);
    };

    var _extendNative = function (tagName, customTagName, definition) {
        var base = document.createElement(tagName).constructor.prototype;
        var prototype = Object.create(base);
        Object.assign(prototype, XElement.mixin);
        Object.defineProperty(prototype, 'selector', { value: tagName + '[is="' + customTagName + '"]' });
        definition(prototype, XElement.mixin, base);

        return _register(customTagName, { prototype: prototype, extends: tagName });
    };

    var _extendCustom = function (T, customTagName, definition) {
        var options = {};
        var selector;
        var selectorParts = T.prototype.selector.split('[');
        var extendsNative = selectorParts.length > 1;
        if (extendsNative) {
            options.extends = selectorParts[0];
            selector = options.extends + '[is="' + customTagName + '"]';
        } else {
            selector = customTagName;
        }
        var base = T.prototype;
        var prototype = Object.create(base);
        Object.defineProperty(prototype, 'selector', { value: selector });
        definition(prototype, XElement.mixin, base);

        options.prototype = prototype;
        return _register(customTagName, options);
    };

    var _register = function (customTagName, options) {

        // Register custom attributes
        var prototype = options.prototype;
        prototype.customAttributes.forEach(function (attrDef) {
            attrDef.addToPrototype(prototype);
        });

        // Register the custom element
        return document.registerElement(customTagName, options);
    };

    return XElement;
});
<|endoftext|>


<commit_before>var AbstractSerializer = require('./AbstractSerializer.js');

var SetSerializer = function SetSerializer(tamp) {

  initTamp(tamp);

  this.tamp = tamp;
};

//Extend the AbstractSerializer
SetSerializer.prototype = Object.create(AbstractSerializer.prototype);
SetSerializer.prototype.AbstractSerializer = AbstractSerializer;

function initTamp(tamp) {
  tamp();
};

SetSerializer.prototype.serialize = function serialize(itemsToSet) {

  throw new Error('set serialization method');

};

SetSerializer.prototype.deserialize = function deserialize() {

  throw new Error('set deserialization method');

};

module.exports = SetSerializer;<commit_msg>Adds some helper methods to SetSerializer constructor object.<commit_after>var AbstractSerializer = require('./AbstractSerializer.js');

var SetSerializer = function SetSerializer(tamp) {

  initTamp(tamp);

  this.tamp = tamp;
};

//Extend the AbstractSerializer
SetSerializer.prototype = Object.create(AbstractSerializer.prototype);
SetSerializer.prototype.AbstractSerializer = AbstractSerializer;

function initTamp(tamp) {
  tamp();
};

SetSerializer.prototype.serialize = function serialize(objectsToSet) {
  var _this = this,
      itemKeys,
      uniqueKeys,
      key;

  var nestedKeys = [],
      hashMap = {};

  var preSerialized = {};
      preSerialized.keys = [];

  objectsToSet.forEach(function(obj){
    if (isObject(obj)) {
      itemKeys = Object.keys(obj);
      nestedKeys.push(itemKeys);

      for (key in obj) {
        //console.log(key, 'Key')
        //console.log(obj[key], 'Value')
        hashMap[key] = obj[key]
        console.log(hashMap, 'hash Map')

      }

    }
    return nestedKeys;
  });

  uniqueKeys = nestedKeys.reduce(toMergedArray).filter(onlyUniqueKeys);

};

function isObject(item) {
  return item === Object(item);
}

function onlyUniqueKeys(value, index, self) {
  return self.indexOf(value) === index;
}

function toMergedArray(a, b) {
  return a.concat(b);
}

SetSerializer.prototype.deserialize = function deserialize() {

  throw new Error('set deserialization method');

};

module.exports = SetSerializer;<|endoftext|>


<commit_before><commit_msg>scroll listener to adjust the position css of the sidebar<commit_after><|endoftext|>


<commit_before>module FS from "node:fs";

import { DataHeader } from "DataHeader.js";
import { DataDescriptor } from "DataDescriptor.js";
import { EntryHeader } from "EntryHeader.js";
import { Crc32 } from "Utilities.js";
import { BufferWriter } from "BufferWriter.js";
import { InflateStream, DeflateStream, NullStream } from "ZipStream.js";

var STORED = 0,
    DEFLATED = 8,
    NO_LOCAL_SIZE = 8,
    MADE_BY_UNIX = 789;

export class ZipEntry {

    constructor(name) {

        this.versionMadeBy = MADE_BY_UNIX;
        this.version = 10;
        this.flags = 0;
        this.method = 0;
        this.lastModified = new Date(0);
        this.crc32 = 0;
        this.compressedSize = 0;
        this.size = 0;
        this.diskNumStart = 0;
        this.inAttr = 0;
        this.attr = 0;
        this.offset = 0;

        this.name = name || "";
        this.extra = null;
        this.comment = "";

        this.source = null;

        if (!this.isDirectory) {

            this.version = 20;
            this.flags = NO_LOCAL_SIZE;
            this.method = DEFLATED;
        }
    }

    get isDirectory() {

        return this.name.endsWith("/");
    }

    readHeader(buffer) {

        // Read the fixed-size portion of the header
        var header = EntryHeader.fromBuffer(buffer),
            offset = EntryHeader.LENGTH;

        // Set entry fields
        Object.keys(header).forEach(key => {

            if (this[key] !== void 0)
                this[key] = header[key];
        });

        // Read file name field
        if (header.fileNameLength) {

            this.name = buffer.toString("utf8", offset, offset += header.fileNameLength);
        }

        // Read extra data fields
        if (header.extraLength) {

            this.extra = new Buffer(header.extraLength);
            buffer.copy(this.extra, 0, offset, offset += header.extraLength);
        }

        // Read entry comment
        if (header.commentLength) {

            this.comment = buffer.toString("utf8", offset, offset += header.commentLength);
        }

        return offset;
    }

    packHeader() {

        this._setLengthFields();

        var buffer = EntryHeader.toBuffer(this),
            writer = new BufferWriter(buffer);

        writer.position = EntryHeader.LENGTH;

        this.name && writer.writeString(this.name);
        this.extra && writer.write(this.extra);
        this.comment && writer.writeString(this.comment);

        return buffer;
    }

    packDataHeader() {

        this._setLengthFields();

        var buffer = DataHeader.toBuffer(this),
            writer = new BufferWriter(buffer);

        writer.position = DataHeader.LENGTH;

        this.name && writer.writeString(this.name);
        this.extra && writer.write(this.extra);

        return buffer;
    }

    packDataDescriptor() {

        return DataDescriptor.toBuffer(this);
    }

    async compress(inStream, outStream, buffer) {

        if (this.isDirectory)
            throw new Error("Entry is not a file");

        var crc = new Crc32(),
            compressed = 0,
            size = 0,
            abort = false,
            zipStream;

        // Create the compression stream
        switch (this.method) {

            case STORED:
                zipStream = new NullStream;
                break;

            case DEFLATED:
                zipStream = new DeflateStream;
                break;

            default:
                throw new Error("Unsupported compression method");
        }

        zipStream.on("data", event => {

            var data = event.data;

            // Accumulate data for the compressed output
            compressed += data.length;

            await outStream.write(data);
        });

        // Store the output position
        this.offset = outStream.position;

        // Write the data header
        await outStream.write(this.packDataHeader());

        var count, data;

        // Read the file, one buffer at a time
        while (count = await inStream.read(buffer)) {

            data = buffer.slice(0, count);

            // Accumulate data for the raw input
            crc.accumulate(data);
            size += count;

            // Write data into compression stream
            await zipStream.write(data);
        }

        await zipStream.end();

        // Set entry data
        this.crc32 = crc.value;
        this.compressedSize = compressed;
        this.size = size;

        await outStream.write(this.packDataDescriptor());

        return this;
    }

    async extract(fileStream, outStream, buffer) {

        if (this.isDirectory)
            throw new Error("Entry is not a file");

        var count, dataHeader;

        // === Read the Data Header ===

        fileStream.seek(this.offset);
        count = await fileStream.read(buffer, 0, DataHeader.LENGTH);
        dataHeader = DataHeader.fromBuffer(buffer);

        fileStream.seek(this.offset + dataHeader.headerSize);

        var zipStream, crc;

        // Create decompression stream
        switch (this.method) {

            case STORED:
                zipStream = new NullStream;
                break;

            case DEFLATED:
                zipStream = new InflateStream;
                crc = new Crc32();
                break;

            default:
                throw new Error("Unsupported compression method");
        }

        zipStream.on("data", event => {

            // Calculate CRC-32 on each chunk of decompressed data
            if (crc)
                crc.accumulate(event.data);

            await outStream.write(event.data);
        });

        // === Decompress Data ==

        var end = fileStream.position + this.compressedSize,
            length,
            data;

        while (fileStream.position < end) {

            // Read the next chunk
            length = Math.min(buffer.length, end - fileStream.position);
            count = await fileStream.read(buffer, 0, length);

            // Write chunk into decompressor
            data = await zipStream.write(buffer.slice(0, count));
        }

        await zipStream.end();

        // === Finalize ===

        // Validate CRC-32
        if (crc && crc.value !== this.crc32)
            throw new Error("CRC-32 check failed.");

        return this;
    }

    _setLengthFields() {

        this.fileNameLength = Buffer.byteLength(this.name);
        this.extraLength = this.extra ? this.extra.length : 0;
        this.commentLength = Buffer.byteLength(this.comment);
    }

}
<commit_msg>Add TODO for correcting buggy streaming.<commit_after>module FS from "node:fs";

import { DataHeader } from "DataHeader.js";
import { DataDescriptor } from "DataDescriptor.js";
import { EntryHeader } from "EntryHeader.js";
import { Crc32 } from "Utilities.js";
import { BufferWriter } from "BufferWriter.js";
import { InflateStream, DeflateStream, NullStream } from "ZipStream.js";

var STORED = 0,
    DEFLATED = 8,
    NO_LOCAL_SIZE = 8,
    MADE_BY_UNIX = 789;

export class ZipEntry {

    constructor(name) {

        this.versionMadeBy = MADE_BY_UNIX;
        this.version = 10;
        this.flags = 0;
        this.method = 0;
        this.lastModified = new Date(0);
        this.crc32 = 0;
        this.compressedSize = 0;
        this.size = 0;
        this.diskNumStart = 0;
        this.inAttr = 0;
        this.attr = 0;
        this.offset = 0;

        this.name = name || "";
        this.extra = null;
        this.comment = "";

        this.source = null;

        if (!this.isDirectory) {

            this.version = 20;
            this.flags = NO_LOCAL_SIZE;
            this.method = DEFLATED;
        }
    }

    get isDirectory() {

        return this.name.endsWith("/");
    }

    readHeader(buffer) {

        // Read the fixed-size portion of the header
        var header = EntryHeader.fromBuffer(buffer),
            offset = EntryHeader.LENGTH;

        // Set entry fields
        Object.keys(header).forEach(key => {

            if (this[key] !== void 0)
                this[key] = header[key];
        });

        // Read file name field
        if (header.fileNameLength) {

            this.name = buffer.toString("utf8", offset, offset += header.fileNameLength);
        }

        // Read extra data fields
        if (header.extraLength) {

            this.extra = new Buffer(header.extraLength);
            buffer.copy(this.extra, 0, offset, offset += header.extraLength);
        }

        // Read entry comment
        if (header.commentLength) {

            this.comment = buffer.toString("utf8", offset, offset += header.commentLength);
        }

        return offset;
    }

    packHeader() {

        this._setLengthFields();

        var buffer = EntryHeader.toBuffer(this),
            writer = new BufferWriter(buffer);

        writer.position = EntryHeader.LENGTH;

        this.name && writer.writeString(this.name);
        this.extra && writer.write(this.extra);
        this.comment && writer.writeString(this.comment);

        return buffer;
    }

    packDataHeader() {

        this._setLengthFields();

        var buffer = DataHeader.toBuffer(this),
            writer = new BufferWriter(buffer);

        writer.position = DataHeader.LENGTH;

        this.name && writer.writeString(this.name);
        this.extra && writer.write(this.extra);

        return buffer;
    }

    packDataDescriptor() {

        return DataDescriptor.toBuffer(this);
    }

    async compress(inStream, outStream, buffer) {

        if (this.isDirectory)
            throw new Error("Entry is not a file");

        var crc = new Crc32(),
            compressed = 0,
            size = 0,
            abort = false,
            zipStream;

        // Create the compression stream
        switch (this.method) {

            case STORED:
                zipStream = new NullStream;
                break;

            case DEFLATED:
                zipStream = new DeflateStream;
                break;

            default:
                throw new Error("Unsupported compression method");
        }

        zipStream.on("data", event => {

            var data = event.data;

            // Accumulate data for the compressed output
            compressed += data.length;

            await outStream.write(data);
        });

        // Store the output position
        this.offset = outStream.position;

        // Write the data header
        await outStream.write(this.packDataHeader());

        var count, data;

        // Read the file, one buffer at a time
        while (count = await inStream.read(buffer)) {

            data = buffer.slice(0, count);

            // Accumulate data for the raw input
            crc.accumulate(data);
            size += count;

            // Write data into compression stream
            await zipStream.write(data);
        }

        await zipStream.end();

        // Set entry data
        this.crc32 = crc.value;
        this.compressedSize = compressed;
        this.size = size;

        await outStream.write(this.packDataDescriptor());

        return this;
    }

    async extract(fileStream, outStream, buffer) {

        if (this.isDirectory)
            throw new Error("Entry is not a file");

        var count, dataHeader;

        // === Read the Data Header ===

        fileStream.seek(this.offset);
        count = await fileStream.read(buffer, 0, DataHeader.LENGTH);
        dataHeader = DataHeader.fromBuffer(buffer);

        fileStream.seek(this.offset + dataHeader.headerSize);

        var zipStream, crc;

        // Create decompression stream
        switch (this.method) {

            case STORED:
                zipStream = new NullStream;
                break;

            case DEFLATED:
                zipStream = new InflateStream;
                crc = new Crc32();
                break;

            default:
                throw new Error("Unsupported compression method");
        }

        zipStream.on("data", event => {

            // Calculate CRC-32 on each chunk of decompressed data
            if (crc)
                crc.accumulate(event.data);

            await outStream.write(event.data);
        });

        // === Decompress Data ==

        var end = fileStream.position + this.compressedSize,
            length,
            data;

        while (fileStream.position < end) {

            // Read the next chunk
            length = Math.min(buffer.length, end - fileStream.position);
            count = await fileStream.read(buffer, 0, length);

            // Write chunk into decompressor
            data = await zipStream.write(buffer.slice(0, count));
        }

        await zipStream.end();

        // TODO:  Sometimes we get here before we've written the last
        // chunk to the file.

        // === Finalize ===

        // Validate CRC-32
        if (crc && crc.value !== this.crc32)
            throw new Error("CRC-32 check failed.");

        return this;
    }

    _setLengthFields() {

        this.fileNameLength = Buffer.byteLength(this.name);
        this.extraLength = this.extra ? this.extra.length : 0;
        this.commentLength = Buffer.byteLength(this.comment);
    }

}
<|endoftext|>


<commit_before>/**
 * # SocketManager
 *
 * Copyright(c) 2012 Stefano Balietti
 * MIT Licensed
 *
 * Game socket manager: registers of all open sockets and
 * connected clients
 *
 * ---
 *
 */

// Global scope

module.exports = SocketManager;

var ngc = require('nodegame-client');
var PlayerList = ngc.PlayerList;
var J = ngc.JSUS;

/**
 * ## SocketManager constructor
 *
 * Creates an instance of SocketManager
 *
 * @param {GameServer} server A GameServer instance
 */
function SocketManager(server) {

    /**
     * ### SocketManager.server
     *
     * Cyclic ref to the GameServer object containing this instance
     *
     */
    this.server = server;

    /**
     * ### SocketManager.sockets
     *
     *  List of available sockets
     */
    this.sockets = {};

    /**
     * ### SocketManager.clients
     *
     * Maps a client ID to a socket obj (e.g. Direct or SIO)
     *
     * @see SocketManager.sockets
     */
    this.clients = {};

    this.sysLogger = server.sysLogger;
    this.msgLogger = server.msgLogger;
}

// ## METHODS

/**
 * SocketManager.registerSocket
 *
 * Registers a new type of socket
 *
 * @param {string} name The name of the socket
 * @param {object} socket The connection socket
 *
 */
SocketManager.prototype.registerSocket = function(name, socket) {
    this.sockets[name] = socket;
};

/**
 * SocketManager.registerClient
 *
 * Matches a client to a socket
 *
 * @param client {string} The id of the client
 * @param socket {object} The socket through which the client is connected
 *
 */
SocketManager.prototype.registerClient = function(client, socket) {
    this.clients[client] = socket;
};

/**
 * ### SocketManager.send
 *
 * Low level primitive to send a game message
 *
 * @param {GameMsg} gameMsg The game message to send
 * @return {boolean} FALSE, if an error occurs
 */
SocketManager.prototype.send = function(gameMsg) {

    switch(gameMsg.to) {

    case 'ALL':
        return this.send2all(gameMsg);

    case 'ROOM':
        // TODO: do we need it?
        return;

    default:
        return this.send2client(gameMsg);

    }

};

/**
 * ### SocketManager.send2all
 *
 * Low level primitive to send a game message
 *
 * @param {GameMsg} gameMsg The game message to send
 * @return {boolean} FALSE, if an error occurs
 */
SocketManager.prototype.send2all = function(gameMsg) {
    var smoothExec, res, i;
    smoothExec = true;

    for (i in this.sockets) {
        if (this.sockets.hasOwnProperty(i)) {
            res = this.sockets[i].send(gameMsg, 'ALL');
            if (!res) {
                this.sysLogger.log('SocketManager.send2all: An error occurred in socket: ' + i, 'ERR');
                smoothExec = false;
            }
        }
    }
    return smoothExec;

};

/**
 * ### SocketManager.send2client
 *
 * Low level primitive to send a game message
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {string} to Optional. The recipient of the message.
 *
 * @return {boolean} FALSE, if an error occurs
 */
SocketManager.prototype.send2client = function(gameMsg, to) {
    var originalTo = gameMsg.to;
    var resolvedInfo, socket, receiverSid;
    var res, errMsg;

    gameMsg.to = to || originalTo;
    resolvedInfo = this.resolveClientAndSocket(gameMsg);

    if (!resolvedInfo.success) {
        gameMsg.to = originalTo;
        this.sysLogger.log('Msg not sent. ' + resolvedInfo.errMsg);
        return false;
    }

    socket = resolvedInfo.socket;
    receiverSid = resolvedInfo.sid;
    res = socket.send(gameMsg, receiverSid);

    if (!res) {
        errMsg = 'An error occurred while sending a message to ' +
            gameMsg.to + ' (cid: ' + resolvedInfo.cid + ', sid: ' + receiverSid + ')';
        this.sysLogger.log(errMsg, 'ERR');
    }
    gameMsg.to = originalTo;
    return res;
};

// TODO check here what kind of data is input

function send2clients(gameMsg, cids) {
    var res;
    res = true;
    for (i = 0; i < cids.length; i++) {
        res = res && this.send2client(gameMsg, cids[i]);
    }
    return res;
}

/**
 * ### SocketManager.send2room
 *
 * Sends a game message to all clients in the same room
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {GameRoom} room. The id or alias of the sender
 * @return {boolean} FALSE is an error occurs
 *
 * TODO: better to have it with the room name?
 */
SocketManager.prototype.send2room = function(gameMsg, room) {
    var res;
    res = send2clients(gameMsg, room.clients.id.getAllKeys());
    if (!res) this.sysLogger.log('SocketManager.send2room: an error occurred for room ' + room.name);
    return res;
};

/**
 * ### SocketManager.send2roomAdmins
 *
 * Sends a game message to all admins in the same room
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {GameRoom} room. The id or alias of the sender
 * @return {boolean} FALSE is an error occurs
 *
 * TODO: better to have it with the room name?
 */
SocketManager.prototype.send2roomAdmins = function(gameMsg, room) {
    var res;
    res = send2clients(gameMsg, room.admins.id.getAllKeys());
    if (!res) this.sysLogger.log('SocketManager.send2roomAdmins: an error has occurred for room ' + room.name);
    return res;
};

/**
 * ### SocketManager.send2roomAdmins
 *
 * Sends a game message to all admins in the same room
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {GameRoom} room. The id or alias of the sender
 * @return {boolean} FALSE is an error occurs
 *
 * TODO: better to have it with the room name?
 */
SocketManager.prototype.send2roomPlayers = function(gameMsg, room) {
    var res;
    res = send2clients(gameMsg, room.players.id.getAllKeys());
    if (!res) this.sysLogger.log('SocketManager.send2roomPlayers: an error has occurred for room ' + room.name);
    return res;
};

/**
 * ### SocketManager.send2group
 *
 * Sends a game message to a group of clients
 *
 * The methods works regardless of the socket or room the clients
 * belong to.
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {array|PlayerList} The recipients of the message
 * @return {boolean} TRUE on success
 */
SocketManager.prototype.send2group = function(gameMsg, group) {
    var res, i;
    res = true;

    if (!J.isArray(group)) {
        group = group.db;
    }
    for (i = 0; i < group.length; i++) {
        res = res && this.send2client(gameMsg, group[i].id);
    }

    if (!res) this.sysLogger.log('SocketManager.send2group: an error has occurred.');
    return res;
};

/**
 * ### SocketManager.resolveClientAndSocket
 *
 * Retrieves the socket, and sid of the receiver of a GameMsg
 *
 * Returns an object with the following properties:
 *
 *  - socket: the Socket object
 *  - sid: the id of the recipient within the socket
 *  - cid: the client id of the recipient within the channel
 *  - success: boolean flag, TRUE if resolving is successful
 *  - errMsg: set if an error occurs
 *
 * @param {GameMsg} gameMsg The game message to analyze
 * @return {object} An object containing the result of the
 *   resolving process
 */
SocketManager.prototype.resolveClientAndSocket = function(gameMsg) {
    var to, socket;
    var resolvedTo, registry;
    var receiverObj, receiverSid;

    registry = this.server.channel.registry;
    to = gameMsg.to;

    // TODO: move somewhere in the wiki
    // Game aliases are global associations with a client id that are valid throughout
    // the whole game, while room-aliases are valid only within a specific room.
    // When a client leaves that room the room alias is deleted.

    // Matches the 'to' (GameMsg.to) field with a client ID.
    // The 'to' field can be already a client ID, or a game-alias or a room-alias.
    // In case of a room-alias, uses 'from' (GameMsg.from) field to locate the room.
    // Can return null if the lookup fails.

    resolvedTo = registry.lookupClient(to,
                                       registry.getClientRoom(gameMsg.from));

    socket = this.clients[resolvedTo];

    if (!socket) {
        return {
            success: false,
            errMsg: 'Unexisting recipient: ' + to + ' (resolved as: ' + resolvedTo + ')'
        };
    }

    receiverObj = registry.getClients()[resolvedTo];

    if (!receiverObj) {
         return {
            success: false,
            errMsg: 'Could not resolve the client id to a socket id'
         };
    }

    receiverSid = receiverObj.sid;

    if (!receiverSid) {
        return {
            success: false,
            errMsg: 'Player object has no socket id.'
        };
    }

    return {
        success: true,
        sid: receiverSid,
        cid: resolvedTo,
        socket: socket
    };
};

/**
 * ### SocketManager.broadcast
 *
 * Sends a game message to all clients in the same room except the sender
 *
 * The sender is taken from the 'from' field of the game message,
 * or it can be specified as a second parameter.
 *
 * TODO: This method at the moment sends message within ROOM only
 * TODO: create a new method that works across ROOMS
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {string} from Optional. The id or alias of the sender. Defaults,
 *    the value of the field gameMsg.from
 * @return {boolean} FALSE is an error occurs
 */
SocketManager.prototype.broadcast = function(gameMsg, from) {
    var client, originalTo, smoothExec;
    var registry = this.server.channel.registry;
    var resolvedFrom;
    var fromRoomName, fromRoom, toClients;

    smoothExec = true;
    originalTo = gameMsg.to;
    from = from || gameMsg.from;

    // Checks if from is an alias, and returns the id.
    // If it is not an alias, it still needs to be found
    // in the registry, otherwise it returns NULL.
    resolvedFrom = registry.lookupClient(from);
    fromRoomName = registry.getClientRoom(resolvedFrom);
    if (!fromRoomName) {
        throw new Error('SocketManager.broadcast: Sender (ID: ' + resolvedFrom + ') is not in any room');
    }
    else {
        fromRoom = this.server.channel.gameRooms[fromRoomName];
        if(!fromRoom) {
            throw new Error('SocketManager.broadcast: Sender (ID: ' + resolvedFrom + ') is not in any room');
        }
        else {
            toClients = fromRoom.clients.id.getAllKeyElements();
        }
    }

    for (client in toClients) {
        if (toClients.hasOwnProperty(client)) {
            if (client != resolvedFrom) {  // TODO: change back to '!==' once 'from' is string again
                gameMsg.to = client;
                smoothExec = smoothExec && this.send(gameMsg);
            }
        }
    }

    // The original _from_ field of the gameMsg must be re-set
    // to avoid that a wrong reuse of the msg in the callee
    gameMsg.to = originalTo;

    return smoothExec;
};

/**
 * ### SocketManager.broadcast2group
 *
 * Broadcasts a game message to a group of clients
 *
 * The methods works regardless of the socket or room the clients
 * belong to.
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {array|PlayerList} The recipients of the message
 * @param {string} from Optional. The id or alias of the sender. Defaults,
 *    the value of the field gameMsg.from
 * @return {boolean} TRUE on success
 */
SocketManager.prototype.broadcast2group = function(gameMsg, group, from) {
    var res, i;
    res = true;
    from = from || gameMsg.from;

    if (!J.isArray(group)) {
        group = group.db;
    }
    for (i = 0; i < group.length; i++) {
        // Do not send the msg to sender as well.
        if (group[i].id === from) continue;
        res = res && this.send2client(gameMsg, group[i].id);
    }

    if (!res) this.sysLogger.log('SocketManager.broadcast2group: an error has occurred.');
    return res;
};

/**
 * ### GameMsgManager.forward
 *
 * Low level primitive to forward messages
 *
 * Forward uses the send method of the partner server to
 * dispatch the message.
 *
 * @param {GameMsg} gameMsg The game message to forward
 */
SocketManager.prototype.forward = function(gameMsg) {

    // <!--
    // Create a copy of the msg and prepare the attributes
    // var gameMsg = new GameMsg(gameMsg);
    // gameMsg.from = this.server.name;
    // --!>

    gameMsg.forward = 1;

    if (gameMsg.to === 'SERVER' || !gameMsg.to) {
        gameMsg.to = 'ALL';
    }

    this.server.partner.socket.send(gameMsg);

    this.msgLogger.log('F', gameMsg);
    this.sysLogger.log('Msg ' + gameMsg.toSMS() + ' forwarded to ' + this.server.partner.name);
};

<commit_msg>SocketManager looks up clients in both GameServers<commit_after>/**
 * # SocketManager
 *
 * Copyright(c) 2012 Stefano Balietti
 * MIT Licensed
 *
 * Game socket manager: registers of all open sockets and
 * connected clients
 *
 * ---
 *
 */

// Global scope

module.exports = SocketManager;

var ngc = require('nodegame-client');
var PlayerList = ngc.PlayerList;
var J = ngc.JSUS;

/**
 * ## SocketManager constructor
 *
 * Creates an instance of SocketManager
 *
 * @param {GameServer} server A GameServer instance
 */
function SocketManager(server) {

    /**
     * ### SocketManager.server
     *
     * Cyclic ref to the GameServer object containing this instance
     *
     */
    this.server = server;

    /**
     * ### SocketManager.sockets
     *
     *  List of available sockets
     */
    this.sockets = {};

    /**
     * ### SocketManager.clients
     *
     * Maps a client ID to a socket obj (e.g. Direct or SIO)
     *
     * @see SocketManager.sockets
     */
    this.clients = {};

    this.sysLogger = server.sysLogger;
    this.msgLogger = server.msgLogger;
}

// ## METHODS

/**
 * SocketManager.registerSocket
 *
 * Registers a new type of socket
 *
 * @param {string} name The name of the socket
 * @param {object} socket The connection socket
 *
 */
SocketManager.prototype.registerSocket = function(name, socket) {
    this.sockets[name] = socket;
};

/**
 * SocketManager.registerClient
 *
 * Matches a client to a socket
 *
 * @param client {string} The id of the client
 * @param socket {object} The socket through which the client is connected
 *
 */
SocketManager.prototype.registerClient = function(client, socket) {
    this.clients[client] = socket;
};

/**
 * ### SocketManager.send
 *
 * Low level primitive to send a game message
 *
 * @param {GameMsg} gameMsg The game message to send
 * @return {boolean} FALSE, if an error occurs
 */
SocketManager.prototype.send = function(gameMsg) {

    switch(gameMsg.to) {

    case 'ALL':
        return this.send2all(gameMsg);

    case 'ROOM':
        // TODO: do we need it?
        return;

    default:
        return this.send2client(gameMsg);

    }

};

/**
 * ### SocketManager.send2all
 *
 * Low level primitive to send a game message
 *
 * @param {GameMsg} gameMsg The game message to send
 * @return {boolean} FALSE, if an error occurs
 */
SocketManager.prototype.send2all = function(gameMsg) {
    var smoothExec, res, i;
    smoothExec = true;

    for (i in this.sockets) {
        if (this.sockets.hasOwnProperty(i)) {
            res = this.sockets[i].send(gameMsg, 'ALL');
            if (!res) {
                this.sysLogger.log('SocketManager.send2all: An error occurred in socket: ' + i, 'ERR');
                smoothExec = false;
            }
        }
    }
    return smoothExec;

};

/**
 * ### SocketManager.send2client
 *
 * Low level primitive to send a game message
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {string} to Optional. The recipient of the message.
 *
 * @return {boolean} FALSE, if an error occurs
 */
SocketManager.prototype.send2client = function(gameMsg, to) {
    var originalTo = gameMsg.to;
    var resolvedInfo, socket, receiverSid;
    var res, errMsg;

    gameMsg.to = to || originalTo;
    resolvedInfo = this.resolveClientAndSocket(gameMsg);

    if (!resolvedInfo.success) {
        gameMsg.to = originalTo;
        this.sysLogger.log('Msg not sent. ' + resolvedInfo.errMsg);
        return false;
    }

    socket = resolvedInfo.socket;
    receiverSid = resolvedInfo.sid;
    res = socket.send(gameMsg, receiverSid);

    if (!res) {
        errMsg = 'An error occurred while sending a message to ' +
            gameMsg.to + ' (cid: ' + resolvedInfo.cid + ', sid: ' + receiverSid + ')';
        this.sysLogger.log(errMsg, 'ERR');
    }
    gameMsg.to = originalTo;
    return res;
};

// TODO check here what kind of data is input

function send2clients(gameMsg, cids) {
    var res;
    res = true;
    for (i = 0; i < cids.length; i++) {
        res = res && this.send2client(gameMsg, cids[i]);
    }
    return res;
}

/**
 * ### SocketManager.send2room
 *
 * Sends a game message to all clients in the same room
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {GameRoom} room. The id or alias of the sender
 * @return {boolean} FALSE is an error occurs
 *
 * TODO: better to have it with the room name?
 */
SocketManager.prototype.send2room = function(gameMsg, room) {
    var res;
    res = send2clients(gameMsg, room.clients.id.getAllKeys());
    if (!res) this.sysLogger.log('SocketManager.send2room: an error occurred for room ' + room.name);
    return res;
};

/**
 * ### SocketManager.send2roomAdmins
 *
 * Sends a game message to all admins in the same room
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {GameRoom} room. The id or alias of the sender
 * @return {boolean} FALSE is an error occurs
 *
 * TODO: better to have it with the room name?
 */
SocketManager.prototype.send2roomAdmins = function(gameMsg, room) {
    var res;
    res = send2clients(gameMsg, room.admins.id.getAllKeys());
    if (!res) this.sysLogger.log('SocketManager.send2roomAdmins: an error has occurred for room ' + room.name);
    return res;
};

/**
 * ### SocketManager.send2roomPlayers
 *
 * Sends a game message to all players in the same room
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {GameRoom} room. The id or alias of the sender
 * @return {boolean} FALSE is an error occurs
 *
 * TODO: better to have it with the room name?
 */
SocketManager.prototype.send2roomPlayers = function(gameMsg, room) {
    var res;
    res = send2clients(gameMsg, room.players.id.getAllKeys());
    if (!res) this.sysLogger.log('SocketManager.send2roomPlayers: an error has occurred for room ' + room.name);
    return res;
};

/**
 * ### SocketManager.send2group
 *
 * Sends a game message to a group of clients
 *
 * The methods works regardless of the socket or room the clients
 * belong to.
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {array|PlayerList} The recipients of the message
 * @return {boolean} TRUE on success
 */
SocketManager.prototype.send2group = function(gameMsg, group) {
    var res, i;
    res = true;

    if (!J.isArray(group)) {
        group = group.db;
    }
    for (i = 0; i < group.length; i++) {
        res = res && this.send2client(gameMsg, group[i].id);
    }

    if (!res) this.sysLogger.log('SocketManager.send2group: an error has occurred.');
    return res;
};

/**
 * ### SocketManager.resolveClientAndSocket
 *
 * Retrieves the socket, and sid of the receiver of a GameMsg
 *
 * Returns an object with the following properties:
 *
 *  - socket: the Socket object
 *  - sid: the id of the recipient within the socket
 *  - cid: the client id of the recipient within the channel
 *  - success: boolean flag, TRUE if resolving is successful
 *  - errMsg: set if an error occurs
 *
 * @param {GameMsg} gameMsg The game message to analyze
 * @return {object} An object containing the result of the
 *   resolving process
 */
SocketManager.prototype.resolveClientAndSocket = function(gameMsg) {
    var to, socket;
    var resolvedTo, registry;
    var receiverObj, receiverSid;

    registry = this.server.channel.registry;
    to = gameMsg.to;

    // TODO: move somewhere in the wiki
    // Game aliases are global associations with a client id that are valid throughout
    // the whole game, while room-aliases are valid only within a specific room.
    // When a client leaves that room the room alias is deleted.

    // Matches the 'to' (GameMsg.to) field with a client ID.
    // The 'to' field can be already a client ID, or a game-alias or a room-alias.
    // In case of a room-alias, uses 'from' (GameMsg.from) field to locate the room.
    // Can return null if the lookup fails.

    resolvedTo = registry.lookupClient(to,
                                       registry.getClientRoom(gameMsg.from));

    socket = this.clients[resolvedTo];

    if (!socket) {
        // Look up in ServerManager of other server:
        socket = this.server.partner.socket.clients[resolvedTo];
        if (!socket) {
            return {
                success: false,
                errMsg: 'Unexisting recipient: ' + to + ' (resolved as: ' + resolvedTo + ')'
            };
        }
    }

    receiverObj = registry.getClients()[resolvedTo];

    if (!receiverObj) {
         return {
            success: false,
            errMsg: 'Could not resolve the client id to a socket id'
         };
    }

    receiverSid = receiverObj.sid;

    if (!receiverSid) {
        return {
            success: false,
            errMsg: 'Player object has no socket id.'
        };
    }

    return {
        success: true,
        sid: receiverSid,
        cid: resolvedTo,
        socket: socket
    };
};

/**
 * ### SocketManager.broadcast
 *
 * Sends a game message to all clients in the same room except the sender
 *
 * The sender is taken from the 'from' field of the game message,
 * or it can be specified as a second parameter.
 *
 * TODO: This method at the moment sends message within ROOM only
 * TODO: create a new method that works across ROOMS
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {string} from Optional. The id or alias of the sender. Defaults,
 *    the value of the field gameMsg.from
 * @return {boolean} FALSE is an error occurs
 */
SocketManager.prototype.broadcast = function(gameMsg, from) {
    var client, originalTo, smoothExec;
    var registry = this.server.channel.registry;
    var resolvedFrom;
    var fromRoomName, fromRoom, toClients;

    smoothExec = true;
    originalTo = gameMsg.to;
    from = from || gameMsg.from;

    // Checks if from is an alias, and returns the id.
    // If it is not an alias, it still needs to be found
    // in the registry, otherwise it returns NULL.
    resolvedFrom = registry.lookupClient(from);
    fromRoomName = registry.getClientRoom(resolvedFrom);
    if (!fromRoomName) {
        throw new Error('SocketManager.broadcast: Sender (ID: ' + resolvedFrom + ') is not in any room');
    }
    else {
        fromRoom = this.server.channel.gameRooms[fromRoomName];
        if(!fromRoom) {
            throw new Error('SocketManager.broadcast: Sender (ID: ' + resolvedFrom + ') is not in any room');
        }
        else {
            toClients = fromRoom.clients.id.getAllKeyElements();
        }
    }

    for (client in toClients) {
        if (toClients.hasOwnProperty(client)) {
            if (client != resolvedFrom) {  // TODO: change back to '!==' once 'from' is string again
                gameMsg.to = client;
                smoothExec = smoothExec && this.send(gameMsg);
            }
        }
    }

    // The original _from_ field of the gameMsg must be re-set
    // to avoid that a wrong reuse of the msg in the callee
    gameMsg.to = originalTo;

    return smoothExec;
};

/**
 * ### SocketManager.broadcast2group
 *
 * Broadcasts a game message to a group of clients
 *
 * The methods works regardless of the socket or room the clients
 * belong to.
 *
 * @param {GameMsg} gameMsg The game message to send
 * @param {array|PlayerList} The recipients of the message
 * @param {string} from Optional. The id or alias of the sender. Defaults,
 *    the value of the field gameMsg.from
 * @return {boolean} TRUE on success
 */
SocketManager.prototype.broadcast2group = function(gameMsg, group, from) {
    var res, i;
    res = true;
    from = from || gameMsg.from;

    if (!J.isArray(group)) {
        group = group.db;
    }
    for (i = 0; i < group.length; i++) {
        // Do not send the msg to sender as well.
        if (group[i].id === from) continue;
        res = res && this.send2client(gameMsg, group[i].id);
    }

    if (!res) this.sysLogger.log('SocketManager.broadcast2group: an error has occurred.');
    return res;
};

/**
 * ### GameMsgManager.forward
 *
 * Low level primitive to forward messages
 *
 * Forward uses the send method of the partner server to
 * dispatch the message.
 *
 * @param {GameMsg} gameMsg The game message to forward
 */
SocketManager.prototype.forward = function(gameMsg) {

    // <!--
    // Create a copy of the msg and prepare the attributes
    // var gameMsg = new GameMsg(gameMsg);
    // gameMsg.from = this.server.name;
    // --!>

    gameMsg.forward = 1;

    if (gameMsg.to === 'SERVER' || !gameMsg.to) {
        gameMsg.to = 'ALL';
    }

    this.server.partner.socket.send(gameMsg);

    this.msgLogger.log('F', gameMsg);
    this.sysLogger.log('Msg ' + gameMsg.toSMS() + ' forwarded to ' + this.server.partner.name);
};

<|endoftext|>