<commit_before>'use strict'

var util = require('util')
var urlModule = require('native-url')
var escape = require('escape-string-regexp')
var CachedPersistence = require('aedes-cached-persistence')
var Packet = CachedPersistence.Packet
var mongodb = require('mongodb')
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)
  }

  opts = opts || {}
  opts.ttl = opts.ttl || {}

  if (typeof opts.ttl.packets === 'number') {
    opts.ttl.packets = {
      retained: opts.ttl.packets,
      will: opts.ttl.packets,
      outgoing: opts.ttl.packets,
      incoming: opts.ttl.packets
    }
  }

  this._opts = opts
  this._db = null
  this._cl = null

  CachedPersistence.call(this, opts)
}

util.inherits(MongoPersistence, CachedPersistence)

MongoPersistence.prototype._connect = function (cb) {
  if (this._opts.db) {
    cb(null, this._opts.db)
    return
  }

  var conn = this._opts.url || 'mongodb://127.0.0.1/aedes'

  // TODO add options
  mongodb.MongoClient.connect(conn, { useNewUrlParser: true, useUnifiedTopology: true }, cb)
}

MongoPersistence.prototype._setup = function () {
  if (this.ready) {
    return
  }

  var that = this

  this._connect(async function (err, client) {
    if (err) {
      this.emit('error', err)
      return
    }

    that._client = client

    var db
    if (that._opts.db) {
      db = that._opts.db
    } else {
      var urlParsed = urlModule.parse(that._opts.url)
      var databaseName = that._opts.database || (urlParsed.pathname ? urlParsed.pathname.substr(1) : undefined)
      db = that._db = client.db(databaseName)
    }

    var subscriptions = db.collection('subscriptions')
    var retained = db.collection('retained')
    var will = db.collection('will')
    var outgoing = db.collection('outgoing')
    var incoming = db.collection('incoming')

    that._cl = {
      subscriptions: subscriptions,
      retained: retained,
      will: will,
      outgoing: outgoing,
      incoming: incoming
    }

    // drop existing indexes
    await subscriptions.dropIndexes()
    await retained.dropIndexes()
    await will.dropIndexes()
    await outgoing.dropIndexes()
    await incoming.dropIndexes()

    if (that._opts.ttl.subscriptions) {
      subscriptions.createIndex({ added: 1 }, { expireAfterSeconds: that._opts.ttl.subscriptions })
    }

    if (that._opts.ttl.packets) {
      if (that._opts.ttl.packets.retained >= 0) {
        retained.createIndex({ added: 1 }, { expireAfterSeconds: that._opts.ttl.packets.retained })
      }

      if (that._opts.ttl.packets.will >= 0) {
        will.createIndex({ added: 1 }, { expireAfterSeconds: that._opts.ttl.packets.will })
      }

      if (that._opts.ttl.packets.outgoing >= 0) {
        outgoing.createIndex({ added: 1 }, { expireAfterSeconds: that._opts.ttl.packets.outgoing })
      }

      if (that._opts.ttl.packets.incoming >= 0) {
        incoming.createIndex({ added: 1 }, { expireAfterSeconds: that._opts.ttl.packets.incoming })
      }
    }

    subscriptions.find({
      qos: { $gte: 0 }
    }).on('data', function (chunk) {
      that._trie.add(chunk.topic, chunk)
    }).on('end', function () {
      that.emit('ready')
    }).on('error', function (err) {
      that.emit('error', err)
    })
  })
}

function decoratePacket (packet, opts) {
  if (opts.ttl.packets) {
    packet.added = new Date()
  }
  return packet
}

MongoPersistence.prototype.storeRetained = function (packet, cb) {
  if (!this.ready) {
    this.once('ready', this.storeRetained.bind(this, packet, cb))
    return
  }
  var criteria = { topic: packet.topic }
  if (packet.payload.length > 0) {
    this._cl.retained.updateOne(criteria, { $set: decoratePacket(packet, this._opts) }, { upsert: true }, cb)
  } else {
    this._cl.retained.deleteOne(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) {
  var actual = escape(pattern).replace(/(#|\\\+).*$/, '')
  return pump(
    this._cl.retained.find({
      topic: new RegExp(actual)
    }),
    filterStream(pattern)
  )
}

function decorateSubscription (sub, opts) {
  if (opts.ttl.subscriptions) {
    sub.added = new Date()
  }
  return sub
}

MongoPersistence.prototype.addSubscriptions = function (client, subs, cb) {
  if (!this.ready) {
    this.once('ready', this.addSubscriptions.bind(this, client, subs, cb))
    return
  }

  var that = this
  var published = 0
  var errored = false
  var bulk = this._cl.subscriptions.initializeOrderedBulkOp()
  subs
    .forEach(function (sub) {
      var subscription = {
        clientId: client.id,
        topic: sub.topic,
        qos: sub.qos
      }
      bulk.find({
        clientId: client.id,
        topic: sub.topic
      }).upsert().updateOne(
        decorateSubscription(subscription, that._opts)
      )
    })

  this._addedSubscriptions(client, subs, function (err) {
    finish(err)
    bulk.execute(finish)
  })

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

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

MongoPersistence.prototype.removeSubscriptions = function (client, subs, cb) {
  if (!this.ready) {
    this.once('ready', this.removeSubscriptions.bind(this, client, subs, cb))
    return
  }

  var published = 0
  var errored = false
  var bulk = this._cl.subscriptions.initializeOrderedBulkOp()
  subs
    .forEach(function (topic) {
      bulk.find({
        clientId: client.id,
        topic: topic
      }).deleteOne()
    })

  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 === 2 && !errored) {
      cb(null, client)
    }
  }
}

MongoPersistence.prototype.subscriptionsByClient = function (client, cb) {
  if (!this.ready) {
    this.once('ready', this.subscriptionsByClient.bind(this, client, cb))
    return
  }

  this._cl.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 clientsCount = 0
  var that = this
  this._cl.subscriptions.aggregate([{
    $group: {
      _id: '$clientId'
    }
  }]).on('data', function () {
    clientsCount++
  }).on('end', function () {
    cb(null, that._trie.subscriptionsCount, clientsCount)
  }).on('error', cb)
}

MongoPersistence.prototype.destroy = function (cb) {
  if (!this.ready) {
    this.once('ready', this.destroy.bind(this, cb))
    return
  }

  if (this._destroyed) {
    throw new Error('destroyed called twice!')
  }

  this._destroyed = true

  cb = cb || noop

  if (this._opts.db) {
    cb()
  } else {
    this._client.close(function () {
      // swallow err in case of close
      cb()
    })
  }
}

MongoPersistence.prototype.outgoingEnqueue = function (sub, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.outgoingEnqueue.bind(this, sub, packet, cb))
    return
  }

  var newp = new Packet(packet)

  this._cl.outgoing.insertOne({
    clientId: sub.clientId,
    packet: decoratePacket(newp, this._opts)
  }, cb)
}

function asPacket (obj, enc, cb) {
  var packet = obj.packet

  if (packet.payload) {
    var buffer = packet.payload.buffer
    if (buffer && Buffer.isBuffer(buffer)) {
      packet.payload = packet.payload.buffer
    }
  }
  cb(null, packet)
}

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

function updateWithMessageId (db, client, packet, cb) {
  db.outgoing.updateOne({
    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.updateOne({
    clientId: client.id,
    'packet.messageId': packet.messageId
  }, {
    $set: {
      clientId: client.id,
      packet: packet
    }
  }, function (err) {
    cb(err, client, packet)
  })
}

MongoPersistence.prototype.outgoingUpdate = function (client, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.outgoingUpdate.bind(this, client, packet, cb))
    return
  }
  if (packet.brokerId) {
    updateWithMessageId(this._cl, client, packet, cb)
  } else {
    updatePacket(this._cl, client, packet, cb)
  }
}

MongoPersistence.prototype.outgoingClearMessageId = function (client, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.outgoingClearMessageId.bind(this, client, packet, cb))
    return
  }

  var outgoing = this._cl.outgoing

  outgoing.findOne({
    clientId: client.id,
    'packet.messageId': packet.messageId
  }, function (err, p) {
    if (err) {
      return cb(err)
    }

    if (!p) {
      return cb(null)
    }

    outgoing.deleteOne({
      clientId: client.id,
      'packet.messageId': packet.messageId
    }, function (err) {
      cb(err, p.packet)
    })
  })
}

MongoPersistence.prototype.incomingStorePacket = function (client, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.incomingStorePacket.bind(this, client, packet, cb))
    return
  }

  var newp = new Packet(packet)
  newp.messageId = packet.messageId

  this._cl.incoming.insertOne({
    clientId: client.id,
    packet: decoratePacket(newp, this._opts)
  }, cb)
}

MongoPersistence.prototype.incomingGetPacket = function (client, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.incomingGetPacket.bind(this, client, packet, cb))
    return
  }

  this._cl.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) {
  if (!this.ready) {
    this.once('ready', this.incomingDelPacket.bind(this, client, packet, cb))
    return
  }

  this._cl.incoming.deleteOne({
    clientId: client.id,
    'packet.messageId': packet.messageId
  }, cb)
}

MongoPersistence.prototype.putWill = function (client, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.putWill.bind(this, client, packet, cb))
    return
  }

  packet.clientId = client.id
  packet.brokerId = this.broker.id
  this._cl.will.insertOne({
    clientId: client.id,
    packet: decoratePacket(packet, this._opts)
  }, function (err) {
    cb(err, client)
  })
}

MongoPersistence.prototype.getWill = function (client, cb) {
  this._cl.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 will = this._cl.will
  this.getWill(client, function (err, packet) {
    if (err || !packet) {
      cb(err, null, client)
      return
    }
    will.deleteOne({
      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._cl.will.find(query), through.obj(asPacket))
}

MongoPersistence.prototype.getClientList = function (topic) {
  var query = {}

  if (topic) {
    query.topic = topic
  }

  return pump(this._cl.subscriptions.find(query), through.obj(function asPacket (obj, enc, cb) {
    this.push(obj.clientId)
    cb()
  }))
}

function noop () { }

module.exports = MongoPersistence
<commit_msg>fix: Drop index on existing collections only<commit_after>'use strict'

var util = require('util')
var urlModule = require('native-url')
var escape = require('escape-string-regexp')
var CachedPersistence = require('aedes-cached-persistence')
var Packet = CachedPersistence.Packet
var mongodb = require('mongodb')
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)
  }

  opts = opts || {}
  opts.ttl = opts.ttl || {}

  if (typeof opts.ttl.packets === 'number') {
    opts.ttl.packets = {
      retained: opts.ttl.packets,
      will: opts.ttl.packets,
      outgoing: opts.ttl.packets,
      incoming: opts.ttl.packets
    }
  }

  this._opts = opts
  this._db = null
  this._cl = null

  CachedPersistence.call(this, opts)
}

util.inherits(MongoPersistence, CachedPersistence)

MongoPersistence.prototype._connect = function (cb) {
  if (this._opts.db) {
    cb(null, this._opts.db)
    return
  }

  var conn = this._opts.url || 'mongodb://127.0.0.1/aedes'

  // TODO add options
  mongodb.MongoClient.connect(conn, { useNewUrlParser: true, useUnifiedTopology: true }, cb)
}

MongoPersistence.prototype._setup = function () {
  if (this.ready) {
    return
  }

  var that = this

  this._connect(async function (err, client) {
    if (err) {
      this.emit('error', err)
      return
    }

    that._client = client

    var db
    if (that._opts.db) {
      db = that._opts.db
    } else {
      var urlParsed = urlModule.parse(that._opts.url)
      var databaseName = that._opts.database || (urlParsed.pathname ? urlParsed.pathname.substr(1) : undefined)
      db = that._db = client.db(databaseName)
    }

    var subscriptions = db.collection('subscriptions')
    var retained = db.collection('retained')
    var will = db.collection('will')
    var outgoing = db.collection('outgoing')
    var incoming = db.collection('incoming')

    that._cl = {
      subscriptions: subscriptions,
      retained: retained,
      will: will,
      outgoing: outgoing,
      incoming: incoming
    }

    var collections = await db.collections()

    for (let i = 0; i < collections.length; i++) {
      if (await collections[i].indexExists('ttl')) {
        await collections[i].dropIndex('ttl')
      }
    }

    if (that._opts.ttl.subscriptions) {
      subscriptions.createIndex({ added: 1 }, { expireAfterSeconds: that._opts.ttl.subscriptions, name: 'ttl' })
    }

    if (that._opts.ttl.packets) {
      if (that._opts.ttl.packets.retained >= 0) {
        retained.createIndex({ added: 1 }, { expireAfterSeconds: that._opts.ttl.packets.retained, name: 'ttl' })
      }

      if (that._opts.ttl.packets.will >= 0) {
        will.createIndex({ added: 1 }, { expireAfterSeconds: that._opts.ttl.packets.will, name: 'ttl' })
      }

      if (that._opts.ttl.packets.outgoing >= 0) {
        outgoing.createIndex({ added: 1 }, { expireAfterSeconds: that._opts.ttl.packets.outgoing, name: 'ttl' })
      }

      if (that._opts.ttl.packets.incoming >= 0) {
        incoming.createIndex({ added: 1 }, { expireAfterSeconds: that._opts.ttl.packets.incoming, name: 'ttl' })
      }
    }

    subscriptions.find({
      qos: { $gte: 0 }
    }).on('data', function (chunk) {
      that._trie.add(chunk.topic, chunk)
    }).on('end', function () {
      that.emit('ready')
    }).on('error', function (err) {
      that.emit('error', err)
    })
  })
}

function decoratePacket (packet, opts) {
  if (opts.ttl.packets) {
    packet.added = new Date()
  }
  return packet
}

MongoPersistence.prototype.storeRetained = function (packet, cb) {
  if (!this.ready) {
    this.once('ready', this.storeRetained.bind(this, packet, cb))
    return
  }
  var criteria = { topic: packet.topic }
  if (packet.payload.length > 0) {
    this._cl.retained.updateOne(criteria, { $set: decoratePacket(packet, this._opts) }, { upsert: true }, cb)
  } else {
    this._cl.retained.deleteOne(criteria, cb)
  }
}

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 this.createRetainedStreamCombi([pattern])
}

MongoPersistence.prototype.createRetainedStreamCombi = function (patterns) {
  var regex = []

  var instance = through.obj(filterPattern)
  instance.matcher = new Qlobber(qlobberOpts)

  for (let i = 0; i < patterns.length; i++) {
    instance.matcher.add(patterns[i], true)
    regex.push(escape(patterns[i]).replace(/(#|\\\+).*$/, ''))
  }

  regex = regex.join('|')

  return pump(
    this._cl.retained.find({
      topic: new RegExp(regex)
    }),
    instance
  )
}

function decorateSubscription (sub, opts) {
  if (opts.ttl.subscriptions) {
    sub.added = new Date()
  }
  return sub
}

MongoPersistence.prototype.addSubscriptions = function (client, subs, cb) {
  if (!this.ready) {
    this.once('ready', this.addSubscriptions.bind(this, client, subs, cb))
    return
  }

  var that = this
  var published = 0
  var errored = false
  var bulk = this._cl.subscriptions.initializeOrderedBulkOp()
  subs
    .forEach(function (sub) {
      var subscription = {
        clientId: client.id,
        topic: sub.topic,
        qos: sub.qos
      }
      bulk.find({
        clientId: client.id,
        topic: sub.topic
      }).upsert().updateOne(
        decorateSubscription(subscription, that._opts)
      )
    })

  this._addedSubscriptions(client, subs, function (err) {
    finish(err)
    bulk.execute(finish)
  })

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

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

MongoPersistence.prototype.removeSubscriptions = function (client, subs, cb) {
  if (!this.ready) {
    this.once('ready', this.removeSubscriptions.bind(this, client, subs, cb))
    return
  }

  var published = 0
  var errored = false
  var bulk = this._cl.subscriptions.initializeOrderedBulkOp()
  subs
    .forEach(function (topic) {
      bulk.find({
        clientId: client.id,
        topic: topic
      }).deleteOne()
    })

  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 === 2 && !errored) {
      cb(null, client)
    }
  }
}

MongoPersistence.prototype.subscriptionsByClient = function (client, cb) {
  if (!this.ready) {
    this.once('ready', this.subscriptionsByClient.bind(this, client, cb))
    return
  }

  this._cl.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 clientsCount = 0
  var that = this
  this._cl.subscriptions.aggregate([{
    $group: {
      _id: '$clientId'
    }
  }]).on('data', function () {
    clientsCount++
  }).on('end', function () {
    cb(null, that._trie.subscriptionsCount, clientsCount)
  }).on('error', cb)
}

MongoPersistence.prototype.destroy = function (cb) {
  if (!this.ready) {
    this.once('ready', this.destroy.bind(this, cb))
    return
  }

  if (this._destroyed) {
    throw new Error('destroyed called twice!')
  }

  this._destroyed = true

  cb = cb || noop

  if (this._opts.db) {
    cb()
  } else {
    this._client.close(function () {
      // swallow err in case of close
      cb()
    })
  }
}

MongoPersistence.prototype.outgoingEnqueue = function (sub, packet, cb) {
  this.outgoingEnqueueCombi([sub], packet, cb)
}

MongoPersistence.prototype.outgoingEnqueueCombi = function (subs, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.outgoingEnqueueCombi.bind(this, subs, packet, cb))
    return
  }

  if (!subs || subs.length === 0) {
    return cb(null, packet)
  }

  var newp = new Packet(packet)
  var opts = this._opts

  function createPacket (sub) {
    return {
      clientId: sub.clientId,
      packet: decoratePacket(newp, opts)
    }
  }

  this._cl.outgoing.insertMany(subs.map(createPacket), function (err) {
    cb(err, packet)
  })
}

function asPacket (obj, enc, cb) {
  var packet = obj.packet

  if (packet.payload) {
    var buffer = packet.payload.buffer
    if (buffer && Buffer.isBuffer(buffer)) {
      packet.payload = packet.payload.buffer
    }
  }
  cb(null, packet)
}

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

function updateWithMessageId (db, client, packet, cb) {
  db.outgoing.updateOne({
    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.updateOne({
    clientId: client.id,
    'packet.messageId': packet.messageId
  }, {
    $set: {
      clientId: client.id,
      packet: packet
    }
  }, function (err) {
    cb(err, client, packet)
  })
}

MongoPersistence.prototype.outgoingUpdate = function (client, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.outgoingUpdate.bind(this, client, packet, cb))
    return
  }
  if (packet.brokerId) {
    updateWithMessageId(this._cl, client, packet, cb)
  } else {
    updatePacket(this._cl, client, packet, cb)
  }
}

MongoPersistence.prototype.outgoingClearMessageId = function (client, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.outgoingClearMessageId.bind(this, client, packet, cb))
    return
  }

  var outgoing = this._cl.outgoing

  outgoing.findOne({
    clientId: client.id,
    'packet.messageId': packet.messageId
  }, function (err, p) {
    if (err) {
      return cb(err)
    }

    if (!p) {
      return cb(null)
    }

    outgoing.deleteOne({
      clientId: client.id,
      'packet.messageId': packet.messageId
    }, function (err) {
      cb(err, p.packet)
    })
  })
}

MongoPersistence.prototype.incomingStorePacket = function (client, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.incomingStorePacket.bind(this, client, packet, cb))
    return
  }

  var newp = new Packet(packet)
  newp.messageId = packet.messageId

  this._cl.incoming.insertOne({
    clientId: client.id,
    packet: decoratePacket(newp, this._opts)
  }, cb)
}

MongoPersistence.prototype.incomingGetPacket = function (client, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.incomingGetPacket.bind(this, client, packet, cb))
    return
  }

  this._cl.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) {
  if (!this.ready) {
    this.once('ready', this.incomingDelPacket.bind(this, client, packet, cb))
    return
  }

  this._cl.incoming.deleteOne({
    clientId: client.id,
    'packet.messageId': packet.messageId
  }, cb)
}

MongoPersistence.prototype.putWill = function (client, packet, cb) {
  if (!this.ready) {
    this.once('ready', this.putWill.bind(this, client, packet, cb))
    return
  }

  packet.clientId = client.id
  packet.brokerId = this.broker.id
  this._cl.will.insertOne({
    clientId: client.id,
    packet: decoratePacket(packet, this._opts)
  }, function (err) {
    cb(err, client)
  })
}

MongoPersistence.prototype.getWill = function (client, cb) {
  this._cl.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 will = this._cl.will
  this.getWill(client, function (err, packet) {
    if (err || !packet) {
      cb(err, null, client)
      return
    }
    will.deleteOne({
      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._cl.will.find(query), through.obj(asPacket))
}

MongoPersistence.prototype.getClientList = function (topic) {
  var query = {}

  if (topic) {
    query.topic = topic
  }

  return pump(this._cl.subscriptions.find(query), through.obj(function asPacket (obj, enc, cb) {
    this.push(obj.clientId)
    cb()
  }))
}

function noop () { }

module.exports = MongoPersistence
<|endoftext|>


<commit_before>/*
 * jQuery gentleSelect plugin
 * http://shawnchin.github.com/jquery-gentleSelect
 *
 * Copyright (c) 2010 Shawn Chin.
 * Licensed under the MIT license.
 *
 * Usage:
 *  (JS)
 *
 *  $('#myselect').gentleSelect(); // default. single column
 *
 * $('#myselect').gentleSelect({ // 3 columns, 150px wide each
 *    itemWidth : 150,
 *    columns   : 3,
 * });
 *
 *  (HTML)
 *  <select id='myselect'><options> .... </options></select>
 *
 */
(function($) {

    var defaults = {
        minWidth  : 100, // only applies if columns and itemWidth not set
        itemWidth : undefined,
        columns   : undefined,
        rows      : undefined,
        title     : undefined,
        prompt    : "Make A Selection",
        maxDisplay: 0,  // set to 0 for unlimited
        openSpeed       : 400,
        closeSpeed      : 400,
        openEffect      : "slide",
        closeEffect     : "slide",
        disallowEmpty   : false,
        hideOnMouseOut  : true
    }

    function defined(obj) {
        if (typeof obj == "undefined") { return false; }
        else { return true; }
    }

    function hasError(c, o) {
        if (defined(o.columns) && defined(o.rows)) {
            $.error("gentleSelect: You cannot supply both 'rows' and 'columns'");
            return true;
        }
        if (defined(o.columns) && !defined(o.itemWidth)) {
            $.error("gentleSelect: itemWidth must be supplied if 'columns' is specified");
            return true;
        }
        if (defined(o.rows) && !defined(o.itemWidth)) {
            $.error("gentleSelect: itemWidth must be supplied if 'rows' is specified");
            return true;
        }
        if (!defined(o.openSpeed) || typeof o.openSpeed != "number" &&
                (typeof o.openSpeed == "string" && (o.openSpeed != "slow" && o.openSpeed != "fast"))) {
            $.error("gentleSelect: openSpeed must be an integer or \"slow\" or \"fast\"");
            return true;
        }
        if (!defined(o.closeSpeed) || typeof o.closeSpeed != "number" &&
                (typeof o.closeSpeed == "string" && (o.closeSpeed != "slow" && o.closeSpeed != "fast"))) {
            $.error("gentleSelect: closeSpeed must be an integer or \"slow\" or \"fast\"");
            return true;
        }
        if (!defined(o.openEffect) || (o.openEffect != "fade" && o.openEffect != "slide")) {
            $.error("gentleSelect: openEffect must be either 'fade' or 'slide'!");
            return true;
        }
        if (!defined(o.closeEffect)|| (o.closeEffect != "fade" && o.closeEffect != "slide")) {
            $.error("gentleSelect: closeEffect must be either 'fade' or 'slide'!");
            return true;
        }
        if (!defined(o.hideOnMouseOut) || (typeof o.hideOnMouseOut != "boolean")) {
            $.error("gentleSelect: hideOnMouseOut must be supplied and either \"true\" or \"false\"!");
            return true;
        }
        return false;
    }

    function optionOverrides(c, o) {
        if (c.attr("multiple")) {
            o.hideOnMouseOut = true; // must be true or dialog will never hide
        }
    }

    function getSelectedAsText(elemList, opts) {
        // If no items selected, return prompt
        if (elemList.length < 1) { return opts.prompt; }

        // Truncate if exceed maxDisplay
        if (opts.maxDisplay != 0 && elemList.length > opts.maxDisplay) {
            var arr = elemList.slice(0, opts.maxDisplay).map(function(){return $(this).text();});
            arr.push("...");
        } else {
            var arr = elemList.map(function(){return $(this).text();});
        }
        return arr.get().join(", ");
    }

    function updateState(c, o) {
        var c = $(c);
        if (c.attr("multiple") && o.disallowEmpty) {
            var all_items = c.data("dialog").find("li"),
                selected_items = all_items.filter(".selected");

            // mark element if only one selected
            all_items.removeClass("sole-selected");
            if (selected_items.length == 1) {
              selected_items.addClass("sole-selected");
            }
        }
    }

    var methods = {
        init : function(options) {
            var o = $.extend({}, defaults, options),
                select_items = this.find("option");

            if (hasError(this, o)) { return this; }; // check for errors
            optionOverrides(this, o); //
            this.hide(); // hide original select box

            if (this.attr("multiple") && o.disallowEmpty)
            {
                if (select_items.length == 0) {
                    $.error("gentleSelect: disallowEmpty conflicts with empty <select>");
                }
                // default to first item if none selected
                if (this[0].selectedIndex < 0) { this[0].selectedIndex = 0; }
            }

            // initialise  to replace select box
            label_text = getSelectedAsText(this.find(":selected"), o);
            var label = $("" + label_text + "")
                .insertBefore(this)
                .bind("mouseenter.gentleselect", event_handlers.labelHoverIn)
                .bind("mouseleave.gentleselect", event_handlers.labelHoverOut)
                .bind("click.gentleselect", event_handlers.labelClick)
                .data("root", this);
            this.data("label", label)
                .data("options", o);

            // generate list of options
            var ul = $("

");
            select_items.each(function() {
                var li = $("
" + $(this).text() + "
")
                    .data("value", $(this).attr("value"))
                    .data("name", $(this).text())
                    .appendTo(ul);
                if ($(this).attr("selected")) { li.addClass("selected"); }
            });

            // build dialog box
            var dialog = $("

")
                .append(ul)
                .insertAfter(label)
                .bind("click.gentleselect", event_handlers.dialogClick)
                .bind("mouseleave.gentleselect", event_handlers.dialogHoverOut)
                .data("label", label)
                .data("root", this);
            this.data("dialog", dialog);

            // if to be displayed in columns
            if (defined(o.columns) || defined(o.rows)) {

                // Update CSS
                ul.css("float", "left")
                    .find("li").width(o.itemWidth).css("float","left");

                var f = ul.find("li:first");
                var actualWidth = o.itemWidth
                    + parseInt(f.css("padding-left"))
                    + parseInt(f.css("padding-right"));
                var elemCount = ul.find("li").length;
                if (defined(o.columns)) {
                    var cols = parseInt(o.columns);
                    var rows = Math.ceil(elemCount / cols);
                } else {
                    var rows = parseInt(o.rows);
                    var cols = Math.ceil(elemCount / rows);
                }
                dialog.width(actualWidth * cols);

                // add padding
                for (var i = 0; i < (rows * cols) - elemCount; i++) {
                    $("

").appendTo(ul);
                }

                // reorder elements
                var ptr = [];
                var idx = 0;
                ul.find("li").each(function() {
                    if (idx < rows) {
                        ptr[idx] = $(this);
                    } else {
                        var p = idx % rows;
                        $(this).insertAfter(ptr[p]);
                        ptr[p] = $(this);
                    }
                    idx++;
                });
            } else if (typeof o.minWidth == "number") {
                dialog.css("min-width", o.minWidth);
            }

            if (defined(o.title)) {
                $("
" + o.title + "
").prependTo(dialog);
            }

            // ESC key should hide all dialog boxes
            $(document).bind("keyup.gentleselect", event_handlers.keyUp);

            updateState(this, o);
            return this;
        },

        // if select box was updated externally, we need to bring everything
        // else up to speed.
        update : function() {
            var opts = this.data("options");

            // Update li with selected data
            var v = (this.attr("multiple")) ? this.val() : [this.val()];
            $("li", this.data("dialog")).each(function() {
                var $li = $(this);
                var isSelected = ($.inArray($li.data("value"), v) != -1);
                $li.toggleClass("selected", isSelected);
            });

            // Update label
            var label = getSelectedAsText(this.find(":selected"), opts);
            this.data("label").text(label);

            updateState(this, opts);
            return this;
        },

        // clear all selections
        clear : function() {
            // Deselect all options.
            // http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-85676760
            this[0].selectedIndex = -1;

            // Update dialog
            return methods["update"].call(this);
        }
    };

    var event_handlers = {

        labelHoverIn : function() {
            $(this).addClass('gentleselect-label-highlight');
        },

        labelHoverOut : function() {
            $(this).removeClass('gentleselect-label-highlight');
        },

        labelClick : function() {
            var $this = $(this);
            var pos = $this.position();
            var root = $this.data("root");
            var opts = root.data("options");
            var dialog = root.data("dialog")
                .css("top", pos.top + $this.height())
                .css("left", pos.left + 1);
            if (opts.openEffect == "fade") {
                dialog.fadeIn(opts.openSpeed);
            } else {
                dialog.slideDown(opts.openSpeed);
            }
        },

        dialogHoverOut : function() {
            var $this = $(this);
            if ($this.data("root").data("options").hideOnMouseOut) {
                $this.hide();
            }
        },

        dialogClick : function(e) {
            var clicked = $(e.target);
            var $this = $(this);
            var root = $this.data("root");
            var opts = root.data("options");
            if (!root.attr("multiple")) {
                if (opts.closeEffect == "fade") {
                    $this.fadeOut(opts.closeSpeed);
                } else {
                    $this.slideUp(opts.closeSpeed);
                }
            }

            if (clicked.is("li") && !clicked.hasClass("gentleselect-dummy")) {
                var value = clicked.data("value");
                var name = clicked.data("name");
                var label = $this.data("label")

                if ($this.data("root").attr("multiple")) {
                    if (opts.disallowEmpty
                          && clicked.hasClass("selected")
                          && (root.find(":selected").length == 1)) {
                        // sole item clicked. For now, do nothing.
                        return;
                    }
                    clicked.toggleClass("selected");
                    var s = $this.find("li.selected");
                    label.text(getSelectedAsText(s, opts));
                    var v = s.map(function(){ return $(this).data("value"); });
                    // update actual selectbox and trigger change event
                    root.val(v.get()).trigger("change");
                    updateState(root, opts);
                } else {
                    $this.find("li.selected").removeClass("selected");
                    clicked.addClass("selected");
                    label.text(clicked.data("name"));
                    // update actual selectbox and trigger change event
                    root.val(value).trigger("change");
                }
            }
        },

        keyUp : function(e) {
            if (e.keyCode == 27 ) { // ESC
                $(".gentleselect-dialog").hide();
            }
        }
    };

    $.fn.gentleSelect = function(method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || ! method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.gentleSelect' );
        }
    };

})(jQuery);
<commit_msg>Fix error in 'update' which occurs when no values are selected<commit_after>/*
 * jQuery gentleSelect plugin
 * http://shawnchin.github.com/jquery-gentleSelect
 *
 * Copyright (c) 2010 Shawn Chin.
 * Licensed under the MIT license.
 *
 * Usage:
 *  (JS)
 *
 *  $('#myselect').gentleSelect(); // default. single column
 *
 * $('#myselect').gentleSelect({ // 3 columns, 150px wide each
 *    itemWidth : 150,
 *    columns   : 3,
 * });
 *
 *  (HTML)
 *  <select id='myselect'><options> .... </options></select>
 *
 */
(function($) {

    var defaults = {
        minWidth  : 100, // only applies if columns and itemWidth not set
        itemWidth : undefined,
        columns   : undefined,
        rows      : undefined,
        title     : undefined,
        prompt    : "Make A Selection",
        maxDisplay: 0,  // set to 0 for unlimited
        openSpeed       : 400,
        closeSpeed      : 400,
        openEffect      : "slide",
        closeEffect     : "slide",
        disallowEmpty   : false,
        hideOnMouseOut  : true
    }

    function defined(obj) {
        if (typeof obj == "undefined") { return false; }
        else { return true; }
    }

    function hasError(c, o) {
        if (defined(o.columns) && defined(o.rows)) {
            $.error("gentleSelect: You cannot supply both 'rows' and 'columns'");
            return true;
        }
        if (defined(o.columns) && !defined(o.itemWidth)) {
            $.error("gentleSelect: itemWidth must be supplied if 'columns' is specified");
            return true;
        }
        if (defined(o.rows) && !defined(o.itemWidth)) {
            $.error("gentleSelect: itemWidth must be supplied if 'rows' is specified");
            return true;
        }
        if (!defined(o.openSpeed) || typeof o.openSpeed != "number" &&
                (typeof o.openSpeed == "string" && (o.openSpeed != "slow" && o.openSpeed != "fast"))) {
            $.error("gentleSelect: openSpeed must be an integer or \"slow\" or \"fast\"");
            return true;
        }
        if (!defined(o.closeSpeed) || typeof o.closeSpeed != "number" &&
                (typeof o.closeSpeed == "string" && (o.closeSpeed != "slow" && o.closeSpeed != "fast"))) {
            $.error("gentleSelect: closeSpeed must be an integer or \"slow\" or \"fast\"");
            return true;
        }
        if (!defined(o.openEffect) || (o.openEffect != "fade" && o.openEffect != "slide")) {
            $.error("gentleSelect: openEffect must be either 'fade' or 'slide'!");
            return true;
        }
        if (!defined(o.closeEffect)|| (o.closeEffect != "fade" && o.closeEffect != "slide")) {
            $.error("gentleSelect: closeEffect must be either 'fade' or 'slide'!");
            return true;
        }
        if (!defined(o.hideOnMouseOut) || (typeof o.hideOnMouseOut != "boolean")) {
            $.error("gentleSelect: hideOnMouseOut must be supplied and either \"true\" or \"false\"!");
            return true;
        }
        return false;
    }

    function optionOverrides(c, o) {
        if (c.attr("multiple")) {
            o.hideOnMouseOut = true; // must be true or dialog will never hide
        }
    }

    function getSelectedAsText(elemList, opts) {
        // If no items selected, return prompt
        if (elemList.length < 1) { return opts.prompt; }

        // Truncate if exceed maxDisplay
        if (opts.maxDisplay != 0 && elemList.length > opts.maxDisplay) {
            var arr = elemList.slice(0, opts.maxDisplay).map(function(){return $(this).text();});
            arr.push("...");
        } else {
            var arr = elemList.map(function(){return $(this).text();});
        }
        return arr.get().join(", ");
    }

    function updateState(c, o) {
        var c = $(c);
        if (c.attr("multiple") && o.disallowEmpty) {
            var all_items = c.data("dialog").find("li"),
                selected_items = all_items.filter(".selected");

            // mark element if only one selected
            all_items.removeClass("sole-selected");
            if (selected_items.length == 1) {
              selected_items.addClass("sole-selected");
            }
        }
    }

    var methods = {
        init : function(options) {
            var o = $.extend({}, defaults, options),
                select_items = this.find("option");

            if (hasError(this, o)) { return this; }; // check for errors
            optionOverrides(this, o); //
            this.hide(); // hide original select box

            if (this.attr("multiple") && o.disallowEmpty)
            {
                if (select_items.length == 0) {
                    $.error("gentleSelect: disallowEmpty conflicts with empty <select>");
                }
                // default to first item if none selected
                if (this[0].selectedIndex < 0) { this[0].selectedIndex = 0; }
            }

            // initialise  to replace select box
            label_text = getSelectedAsText(this.find(":selected"), o);
            var label = $("" + label_text + "")
                .insertBefore(this)
                .bind("mouseenter.gentleselect", event_handlers.labelHoverIn)
                .bind("mouseleave.gentleselect", event_handlers.labelHoverOut)
                .bind("click.gentleselect", event_handlers.labelClick)
                .data("root", this);
            this.data("label", label)
                .data("options", o);

            // generate list of options
            var ul = $("

");
            select_items.each(function() {
                var li = $("
" + $(this).text() + "
")
                    .data("value", $(this).attr("value"))
                    .data("name", $(this).text())
                    .appendTo(ul);
                if ($(this).attr("selected")) { li.addClass("selected"); }
            });

            // build dialog box
            var dialog = $("

")
                .append(ul)
                .insertAfter(label)
                .bind("click.gentleselect", event_handlers.dialogClick)
                .bind("mouseleave.gentleselect", event_handlers.dialogHoverOut)
                .data("label", label)
                .data("root", this);
            this.data("dialog", dialog);

            // if to be displayed in columns
            if (defined(o.columns) || defined(o.rows)) {

                // Update CSS
                ul.css("float", "left")
                    .find("li").width(o.itemWidth).css("float","left");

                var f = ul.find("li:first");
                var actualWidth = o.itemWidth
                    + parseInt(f.css("padding-left"))
                    + parseInt(f.css("padding-right"));
                var elemCount = ul.find("li").length;
                if (defined(o.columns)) {
                    var cols = parseInt(o.columns);
                    var rows = Math.ceil(elemCount / cols);
                } else {
                    var rows = parseInt(o.rows);
                    var cols = Math.ceil(elemCount / rows);
                }
                dialog.width(actualWidth * cols);

                // add padding
                for (var i = 0; i < (rows * cols) - elemCount; i++) {
                    $("

").appendTo(ul);
                }

                // reorder elements
                var ptr = [];
                var idx = 0;
                ul.find("li").each(function() {
                    if (idx < rows) {
                        ptr[idx] = $(this);
                    } else {
                        var p = idx % rows;
                        $(this).insertAfter(ptr[p]);
                        ptr[p] = $(this);
                    }
                    idx++;
                });
            } else if (typeof o.minWidth == "number") {
                dialog.css("min-width", o.minWidth);
            }

            if (defined(o.title)) {
                $("
" + o.title + "
").prependTo(dialog);
            }

            // ESC key should hide all dialog boxes
            $(document).bind("keyup.gentleselect", event_handlers.keyUp);

            updateState(this, o);
            return this;
        },

        // if select box was updated externally, we need to bring everything
        // else up to speed.
        update : function() {
            var opts = this.data("options");

            // Update li with selected data
            var v = (this.attr("multiple") && this.val()) ? this.val() : [this.val()];
            $("li", this.data("dialog")).each(function() {
                var $li = $(this);
                var isSelected = ($.inArray($li.data("value"), v) != -1);
                $li.toggleClass("selected", isSelected);
            });

            // Update label
            var label = getSelectedAsText(this.find(":selected"), opts);
            this.data("label").text(label);

            updateState(this, opts);
            return this;
        },

        // clear all selections
        clear : function() {
            // Deselect all options.
            // http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-85676760
            this[0].selectedIndex = -1;

            // Update dialog
            return methods["update"].call(this);
        }
    };

    var event_handlers = {

        labelHoverIn : function() {
            $(this).addClass('gentleselect-label-highlight');
        },

        labelHoverOut : function() {
            $(this).removeClass('gentleselect-label-highlight');
        },

        labelClick : function() {
            var $this = $(this);
            var pos = $this.position();
            var root = $this.data("root");
            var opts = root.data("options");
            var dialog = root.data("dialog")
                .css("top", pos.top + $this.height())
                .css("left", pos.left + 1);
            if (opts.openEffect == "fade") {
                dialog.fadeIn(opts.openSpeed);
            } else {
                dialog.slideDown(opts.openSpeed);
            }
        },

        dialogHoverOut : function() {
            var $this = $(this);
            if ($this.data("root").data("options").hideOnMouseOut) {
                $this.hide();
            }
        },

        dialogClick : function(e) {
            var clicked = $(e.target);
            var $this = $(this);
            var root = $this.data("root");
            var opts = root.data("options");
            if (!root.attr("multiple")) {
                if (opts.closeEffect == "fade") {
                    $this.fadeOut(opts.closeSpeed);
                } else {
                    $this.slideUp(opts.closeSpeed);
                }
            }

            if (clicked.is("li") && !clicked.hasClass("gentleselect-dummy")) {
                var value = clicked.data("value");
                var name = clicked.data("name");
                var label = $this.data("label")

                if ($this.data("root").attr("multiple")) {
                    if (opts.disallowEmpty
                          && clicked.hasClass("selected")
                          && (root.find(":selected").length == 1)) {
                        // sole item clicked. For now, do nothing.
                        return;
                    }
                    clicked.toggleClass("selected");
                    var s = $this.find("li.selected");
                    label.text(getSelectedAsText(s, opts));
                    var v = s.map(function(){ return $(this).data("value"); });
                    // update actual selectbox and trigger change event
                    root.val(v.get()).trigger("change");
                    updateState(root, opts);
                } else {
                    $this.find("li.selected").removeClass("selected");
                    clicked.addClass("selected");
                    label.text(clicked.data("name"));
                    // update actual selectbox and trigger change event
                    root.val(value).trigger("change");
                }
            }
        },

        keyUp : function(e) {
            if (e.keyCode == 27 ) { // ESC
                $(".gentleselect-dialog").hide();
            }
        }
    };

    $.fn.gentleSelect = function(method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || ! method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.gentleSelect' );
        }
    };

})(jQuery);
<|endoftext|>


<commit_before>/*
* Copyright 2012 Kapitanluffy Pirata
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

var form_validation = new (function() {

	var self = this;
	var fvuuid = 0;

	this.rules = {}

	this.set_rule = function(name, rule, object) {
		if( self.rules[name] == undefined){
			self.rules[name] = {};
		}
		self.rules[name][rule] = object;
	}

	/* main method */
	this.run = function(selector, callback) {

		try {
			var callback = callback || function() {};
			var selector = selector || 'body';
			var errors = [];

			for(var input in self.rules) {

				for(var ktn in self.rules[input]) {
					var input_selector = $(selector).find('[name="'+ input +'"]');
					var rule_object = self.rules[input];

					$(input_selector).each(function(i,e){
						var result = true;
						var uuid = 'fv-' + ++fvuuid;
						$(e).data('fvuuid', uuid);
						$(e).addClass('fv-item');

						switch(ktn.toLowerCase()) {
							case 'required':
								rule_object[ktn]['result'] = is_required(e);
							break;
							case 'number':
								rule_object[ktn]['result'] = is_number(e);
							break;
						}

						if(! rule_object[ktn]['result']) {
							rule_object[ktn]['name'] = ktn.toLowerCase();

							var error = { 'element': e, 'rule': rule_object[ktn], 'uuid': uuid }
							errors.push(error);
						}

						if(typeof(rule_object[ktn]['callback']) == 'function') {
							rule_object[ktn]['callback'](e, rule_object[ktn], uuid);
						}
					});
				}
			}

			errors.length = count(errors);

			if(typeof(callback) == 'function') {
				callback(errors);
			}

			return errors;
		} catch(kpt) {
			console.groupCollapsed('Exception: assumed reason: wrong rules format');
			console.error(kpt);
			console.groupEnd();
		}
	}

	/* rule methods */
	function is_required(selector) {
		if(! $(selector).prop('disabled')) {
			return ($(selector).val() != '');
		}
		return true;
	}

	function is_number(selector) {
		var regex = /[^0-9\.]/i
		var value = $(selector).val();
		return !regex.test(value)
	}

	/* utility methods */
	function count(ktn) {
		var count = 0;
		for (var kpt in ktn) {
			if (ktn.hasOwnProperty(kpt)) {
				++count;
			}
		}
		return count;
	}
 })
<commit_msg>Fixed generation of uuid Added valid_date() and number_no_zero() rules Rules can now be callback functions<commit_after>/*
* Copyright 2012 Kapitanluffy Pirata
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

var form_validation = new (function() {

	var self = this;
	var fvuuid = 0;
	this.rules = {}

	this.set_rule = function(name, rule, object) {

		if( self.rules[name] == undefined){
			self.rules[name] = [];
		}

		self.rules[name].push(rule);
	}

	/* main method */
	this.run = function(selector, callback) {

		try {
			var callback = callback || function() {};
			var selector = selector || 'body';
			var errors = {};

			for(var inputs in self.rules) {

				$(self.rules[inputs]).each(function(i,input){
					var input_selector = $(selector).find('[name="'+ inputs +'"]');
					var rule_object = input;
					var rule_name = input['name'];

					// console.log(self.rules);
					if(typeof rule_name == 'function') {
						rule_name = 'function';
					}

					$(input_selector).each(function(i,e){
						if(errors[inputs]) {
							return;
						}

						var result = true;
						$(e).addClass('fv-item');

						/* generate uuid for this element */
						var uuid = $(e).data('fvuuid');
						if( uuid == undefined) {
							uuid = 'fv-' + inputs.replace(/\[\]/,'') + '-' + ++fvuuid;
							$(e).data('fvuuid', uuid);
						}

						/* run the rules */
						switch(rule_name) {
							case 'required':
								rule_object['result'] = is_required(e);
							break;
							case 'valid_date':
								rule_object['result'] = is_valid_date(e);
							break;
							case 'number':
								rule_object['result'] = is_number(e);
							break;
							case 'number_no_zero':
								rule_object['result'] = is_number_no_zero(e);
							break;
							case 'function':
								rule_object['result'] = input['name'](e);
							break;
						}

						if(typeof(rule_object['callback']) == 'function') {
							rule_object['callback'](e, rule_object, uuid);
						}

						if(! rule_object['result']) {
							var error = { 'element': e, 'rule': rule_object, 'uuid': uuid }
							errors[inputs] = error;
						}

					});
				});
			}

			errors.length = count(errors);

			if(typeof(callback) == 'function') {
				callback(errors);
			}

			return errors;
		} catch(kpt) {
			console.groupCollapsed('Exception: assumed reason: wrong rules format');
			console.error(kpt);
			console.groupEnd();
		}
	}

	/* rule methods */
	function is_required(selector) {
		if(! $(selector).prop('disabled')) {
			return ($(selector).val() != '');
		}
		return true;
	}

	function is_number(selector) {
		var regex = /[^0-9\.]/i
		var value = $(selector).val();
		return !regex.test(value)
	}

	function is_number_no_zero(selector) {
		var regex = /[^1-9\.]/i
		var value = $(selector).val();
		return !regex.test(value)
	}

	function is_valid_date(selector) {

		return (new Date($(selector).val()) != 'Invalid Date')
	}

	/* utility methods */
	function count(ktn) {
		var count = 0;
		for (var kpt in ktn) {
			if (ktn.hasOwnProperty(kpt)) {
				++count;
			}
		}
		return count;
	}
 })
<|endoftext|>


<commit_before><commit_msg>[www/js/ui] change submit button text to say<commit_after><|endoftext|>


<commit_before>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
"use strict";
let nextIdent = 0;

class CommonsChunkPlugin {
	constructor(options) {
		if(arguments.length > 1) {
			throw new Error(`Deprecation notice: CommonsChunkPlugin now only takes a single argument. Either an options
					object *or* the name of the chunk.
					Example: if your old code looked like this:
						new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js')

					You would change it to:
						new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.bundle.js' })

					The available options are:
						name: string
						names: string[]
						filename: string
						minChunks: number
						chunks: string[]
						children: boolean
						async: boolean
						minSize: number`);
		}
		if(Array.isArray(options) || typeof options === "string") {
			options = {
				name: options
			};
		}
		this.chunkNames = options.name || options.names;
		this.filenameTemplate = options.filename;
		this.minChunks = options.minChunks;
		this.selectedChunks = options.chunks;
		if(options.children) this.selectedChunks = false;
		this.async = options.async;
		this.minSize = options.minSize;
		this.ident = __filename + (nextIdent++);
	}
	apply(compiler) {
		const chunkNames = this.chunkNames;
		const filenameTemplate = this.filenameTemplate;
		const minChunks = this.minChunks;
		const selectedChunks = this.selectedChunks;
		const asyncOption = this.async;
		const minSize = this.minSize;
		const ident = this.ident;
		compiler.plugin("this-compilation", (compilation) => {
			compilation.plugin(["optimize-chunks", "optimize-extracted-chunks"], (chunks) => {
				// only optimize once
				if(compilation[ident]) return;
				compilation[ident] = true;

				let commonChunks;
				if(!chunkNames && (selectedChunks === false || asyncOption)) {
					commonChunks = chunks;
				} else if(Array.isArray(chunkNames) || typeof chunkNames === "string") {
					commonChunks = [].concat(chunkNames).map((chunkName) => {
						let chunk = chunks.filter((chunk) => {
							return chunk.name === chunkName;
						})[0];
						if(!chunk) {
							chunk = compilation.addChunk(chunkName);
						}
						return chunk;
					}, this);
				} else {
					throw new Error("Invalid chunkNames argument");
				}
				commonChunks.forEach(function processCommonChunk(commonChunk, idx) {
					let usedChunks;
					if(Array.isArray(selectedChunks)) {
						usedChunks = chunks.filter((chunk) => {
							if(chunk === commonChunk) return false;
							return selectedChunks.indexOf(chunk.name) >= 0;
						});
					} else if(selectedChunks === false || asyncOption) {
						usedChunks = (commonChunk.chunks || []).filter((chunk) => {
							// we can only move modules from this chunk if the "commonChunk" is the only parent
							return asyncOption || chunk.parents.length === 1;
						});
					} else {
						if(commonChunk.parents.length > 0) {
							compilation.errors.push(new Error("CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk (" + commonChunk.name + ")"));
							return;
						}
						usedChunks = chunks.filter((chunk) => {
							const found = commonChunks.indexOf(chunk);
							if(found >= idx) return false;
							return chunk.hasRuntime();
						});
					}
					let asyncChunk;
					if(asyncOption) {
						asyncChunk = compilation.addChunk(typeof asyncOption === "string" ? asyncOption : undefined);
						asyncChunk.chunkReason = "async commons chunk";
						asyncChunk.extraAsync = true;
						asyncChunk.addParent(commonChunk);
						commonChunk.addChunk(asyncChunk);
						commonChunk = asyncChunk;
					}
					const reallyUsedModules = [];
					if(minChunks !== Infinity) {
						const commonModulesCount = [];
						const commonModules = [];
						usedChunks.forEach((chunk) => {
							chunk.modules.forEach((module) => {
								const idx = commonModules.indexOf(module);
								if(idx < 0) {
									commonModules.push(module);
									commonModulesCount.push(1);
								} else {
									commonModulesCount[idx]++;
								}
							});
						});
						const _minChunks = (minChunks || Math.max(2, usedChunks.length))
						commonModulesCount.forEach((count, idx) => {
							const module = commonModules[idx];
							if(typeof minChunks === "function") {
								if(!minChunks(module, count))
									return;
							} else if(count < _minChunks) {
								return;
							}
							if(module.chunkCondition && !module.chunkCondition(commonChunk))
								return;
							reallyUsedModules.push(module);
						});
					}
					if(minSize) {
						const size = reallyUsedModules.reduce((a, b) => {
							return a + b.size();
						}, 0);
						if(size < minSize)
							return;
					}
					const reallyUsedChunks = [];
					reallyUsedModules.forEach((module) => {
						usedChunks.forEach((chunk) => {
							if(module.removeChunk(chunk)) {
								if(reallyUsedChunks.indexOf(chunk) < 0)
									reallyUsedChunks.push(chunk);
							}
						});
						commonChunk.addModule(module);
						module.addChunk(commonChunk);
					});
					if(asyncOption) {
						reallyUsedChunks.forEach((chunk) => {
							if(chunk.isInitial())
								return;
							chunk.blocks.forEach((block) => {
								block.chunks.unshift(commonChunk);
								commonChunk.addBlock(block);
							});
						});
						asyncChunk.origins = reallyUsedChunks.map((chunk) => {
							return chunk.origins.map((origin) => {
								const newOrigin = Object.create(origin);
								newOrigin.reasons = (origin.reasons || []).slice();
								newOrigin.reasons.push("async commons");
								return newOrigin;
							});
						}).reduce((arr, a) => {
							arr.push.apply(arr, a);
							return arr;
						}, []);
					} else {
						usedChunks.forEach((chunk) => {
							chunk.parents = [commonChunk];
							chunk.entrypoints.forEach((ep) => {
								ep.insertChunk(commonChunk, chunk);
							});
							commonChunk.addChunk(chunk);
						});
					}
					if(filenameTemplate)
						commonChunk.filenameTemplate = filenameTemplate;
				}, this);
				return true;
			});
		});
	}
}

module.exports = CommonsChunkPlugin;
<commit_msg>formatted the template string to remove extra whitespace and line breaks<commit_after>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
"use strict";
let nextIdent = 0;

class CommonsChunkPlugin {
	constructor(options) {
		if(arguments.length > 1) {
			throw new Error(`Deprecation notice: CommonsChunkPlugin now only takes a single argument. Either an options
object *or* the name of the chunk.
Example: if your old code looked like this:
	new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js')

You would change it to:
	new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.bundle.js' })

The available options are:
	name: string
	names: string[]
	filename: string
	minChunks: number
	chunks: string[]
	children: boolean
	async: boolean
	minSize: number`);
		}

		if(Array.isArray(options) || typeof options === "string") {
			options = {
				name: options
			};
		}
		this.chunkNames = options.name || options.names;
		this.filenameTemplate = options.filename;
		this.minChunks = options.minChunks;
		this.selectedChunks = options.chunks;
		if(options.children) this.selectedChunks = false;
		this.async = options.async;
		this.minSize = options.minSize;
		this.ident = __filename + (nextIdent++);
	}
	apply(compiler) {
		const chunkNames = this.chunkNames;
		const filenameTemplate = this.filenameTemplate;
		const minChunks = this.minChunks;
		const selectedChunks = this.selectedChunks;
		const asyncOption = this.async;
		const minSize = this.minSize;
		const ident = this.ident;
		compiler.plugin("this-compilation", (compilation) => {
			compilation.plugin(["optimize-chunks", "optimize-extracted-chunks"], (chunks) => {
				// only optimize once
				if(compilation[ident]) return;
				compilation[ident] = true;

				let commonChunks;
				if(!chunkNames && (selectedChunks === false || asyncOption)) {
					commonChunks = chunks;
				} else if(Array.isArray(chunkNames) || typeof chunkNames === "string") {
					commonChunks = [].concat(chunkNames).map((chunkName) => {
						let chunk = chunks.filter((chunk) => {
							return chunk.name === chunkName;
						})[0];
						if(!chunk) {
							chunk = compilation.addChunk(chunkName);
						}
						return chunk;
					}, this);
				} else {
					throw new Error("Invalid chunkNames argument");
				}
				commonChunks.forEach(function processCommonChunk(commonChunk, idx) {
					let usedChunks;
					if(Array.isArray(selectedChunks)) {
						usedChunks = chunks.filter((chunk) => {
							if(chunk === commonChunk) return false;
							return selectedChunks.indexOf(chunk.name) >= 0;
						});
					} else if(selectedChunks === false || asyncOption) {
						usedChunks = (commonChunk.chunks || []).filter((chunk) => {
							// we can only move modules from this chunk if the "commonChunk" is the only parent
							return asyncOption || chunk.parents.length === 1;
						});
					} else {
						if(commonChunk.parents.length > 0) {
							compilation.errors.push(new Error("CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk (" + commonChunk.name + ")"));
							return;
						}
						usedChunks = chunks.filter((chunk) => {
							const found = commonChunks.indexOf(chunk);
							if(found >= idx) return false;
							return chunk.hasRuntime();
						});
					}
					let asyncChunk;
					if(asyncOption) {
						asyncChunk = compilation.addChunk(typeof asyncOption === "string" ? asyncOption : undefined);
						asyncChunk.chunkReason = "async commons chunk";
						asyncChunk.extraAsync = true;
						asyncChunk.addParent(commonChunk);
						commonChunk.addChunk(asyncChunk);
						commonChunk = asyncChunk;
					}
					const reallyUsedModules = [];
					if(minChunks !== Infinity) {
						const commonModulesCount = [];
						const commonModules = [];
						usedChunks.forEach((chunk) => {
							chunk.modules.forEach((module) => {
								const idx = commonModules.indexOf(module);
								if(idx < 0) {
									commonModules.push(module);
									commonModulesCount.push(1);
								} else {
									commonModulesCount[idx]++;
								}
							});
						});
						const _minChunks = (minChunks || Math.max(2, usedChunks.length))
						commonModulesCount.forEach((count, idx) => {
							const module = commonModules[idx];
							if(typeof minChunks === "function") {
								if(!minChunks(module, count))
									return;
							} else if(count < _minChunks) {
								return;
							}
							if(module.chunkCondition && !module.chunkCondition(commonChunk))
								return;
							reallyUsedModules.push(module);
						});
					}
					if(minSize) {
						const size = reallyUsedModules.reduce((a, b) => {
							return a + b.size();
						}, 0);
						if(size < minSize)
							return;
					}
					const reallyUsedChunks = [];
					reallyUsedModules.forEach((module) => {
						usedChunks.forEach((chunk) => {
							if(module.removeChunk(chunk)) {
								if(reallyUsedChunks.indexOf(chunk) < 0)
									reallyUsedChunks.push(chunk);
							}
						});
						commonChunk.addModule(module);
						module.addChunk(commonChunk);
					});
					if(asyncOption) {
						reallyUsedChunks.forEach((chunk) => {
							if(chunk.isInitial())
								return;
							chunk.blocks.forEach((block) => {
								block.chunks.unshift(commonChunk);
								commonChunk.addBlock(block);
							});
						});
						asyncChunk.origins = reallyUsedChunks.map((chunk) => {
							return chunk.origins.map((origin) => {
								const newOrigin = Object.create(origin);
								newOrigin.reasons = (origin.reasons || []).slice();
								newOrigin.reasons.push("async commons");
								return newOrigin;
							});
						}).reduce((arr, a) => {
							arr.push.apply(arr, a);
							return arr;
						}, []);
					} else {
						usedChunks.forEach((chunk) => {
							chunk.parents = [commonChunk];
							chunk.entrypoints.forEach((ep) => {
								ep.insertChunk(commonChunk, chunk);
							});
							commonChunk.addChunk(chunk);
						});
					}
					if(filenameTemplate)
						commonChunk.filenameTemplate = filenameTemplate;
				}, this);
				return true;
			});
		});
	}
}

module.exports = CommonsChunkPlugin;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import injectStyles from 'react-jss';
import ReactPlayer from 'react-player';
import Modal from 'react-modal';
import headerImage from '../img/header-image_860x391.jpg';
import logo from '../img/full-on-2020-logo.svg'
import config from '../../config';
import { button, gutters, contentWidth } from './global-style';

const modalStyles = {
    content: {
        backgroundColor: 'black',
        border: 'none',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
    },
    overlay: {
        position: 'fixed',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        backgroundColor: 'black',
    },
};

const style = {
    container: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        backgroundSize: 'contain',
    },
    logo: {
        maxWidth: contentWidth,
        width: '100%',
        marginBottom: gutters.medium,
        marginTop: 0,
        overflow: 'hidden'
    },
    button,
    closeModalButton: {
        ...button,
        display: 'block',
        marginLeft: 'auto',
        marginRight: 'auto',
        marginTop: gutters.large * 2,
    },
};

@injectStyles(style)
class Header extends Component {
    state = {
        isModalOpen: false,
    };

    closeModal = () => {
        this.setState({
            isModalOpen: false,
        });
    };

    openModal = () => {
        this.setState({
            isModalOpen: true,
        });
    };

    render() {
        const {sheet: {classes}} = this.props;
        const {isModalOpen} = this.state;

        return (

                <Modal
                    isOpen={isModalOpen}
                    onRequestClose={this.closeModal}
                    style={modalStyles}
                >
                    <ReactPlayer
                        url={config.trailerUrl}
                        playing
                        onEnded={this.closeModal}
                        width={800}
                        height={450}
                        style={{maxWidth: '100vw'}}
                    />
                    <button
                        className={classes.closeModalButton}
                        onClick={this.closeModal}
                    >
                        Close Video
                    </button>
                </Modal>
                <button
                    onClick={this.openModal}
                    className={classes.button}
                >
                    What is Full On? (Video)
                </button>

        );
    }
}

export default Header;
<commit_msg>fix: logo position<commit_after>import React, { Component } from 'react';
import injectStyles from 'react-jss';
import ReactPlayer from 'react-player';
import Modal from 'react-modal';
import headerImage from '../img/header-image_860x391.jpg';
import logo from '../img/full-on-2020-logo.svg'
import config from '../../config';
import { button, gutters, contentWidth } from './global-style';
import { relative } from 'path';

const modalStyles = {
    content: {
        backgroundColor: 'black',
        border: 'none',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
    },
    overlay: {
        position: 'fixed',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        backgroundColor: 'black',
    },
};

const style = {
    container: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        backgroundSize: 'contain',
    },
    logo: {
        position: 'relative',
        maxWidth: contentWidth,
        width: '100%',
        marginBottom: gutters.medium,
        marginTop: 0,
        overflow: 'hidden'
    },
    button,
    closeModalButton: {
        ...button,
        display: 'block',
        marginLeft: 'auto',
        marginRight: 'auto',
        marginTop: gutters.large * 2,
    },
};

@injectStyles(style)
class Header extends Component {
    state = {
        isModalOpen: false,
    };

    closeModal = () => {
        this.setState({
            isModalOpen: false,
        });
    };

    openModal = () => {
        this.setState({
            isModalOpen: true,
        });
    };

    render() {
        const {sheet: {classes}} = this.props;
        const {isModalOpen} = this.state;

        return (

                <Modal
                    isOpen={isModalOpen}
                    onRequestClose={this.closeModal}
                    style={modalStyles}
                >
                    <ReactPlayer
                        url={config.trailerUrl}
                        playing
                        onEnded={this.closeModal}
                        width={800}
                        height={450}
                        style={{maxWidth: '100vw'}}
                    />
                    <button
                        className={classes.closeModalButton}
                        onClick={this.closeModal}
                    >
                        Close Video
                    </button>
                </Modal>
                <button
                    onClick={this.openModal}
                    className={classes.button}
                >
                    What is Full On? (Video)
                </button>

        );
    }
}

export default Header;
<|endoftext|>