<commit_before>#!/usr/bin/env node

var optimist = require('optimist')
var rc = require('rc')
var clivas = require('clivas')
var numeral = require('numeral')
var os = require('os')
var address = require('network-address')
var readTorrent = require('read-torrent')
var proc = require('child_process')
var peerflix = require('./')
var keypress = require('keypress')
var open = require('open')

var path = require('path')

process.title = 'peerflix'

var argv = rc('peerflix', {}, optimist
  .usage('Usage: $0 magnet-link-or-torrent [options]')
  .alias('c', 'connections').describe('c', 'max connected peers').default('c', os.cpus().length > 1 ? 100 : 30)
  .alias('p', 'port').describe('p', 'change the http port').default('p', 8888)
  .alias('i', 'index').describe('i', 'changed streamed file (index)')
  .alias('l', 'list').describe('l', 'list available files with corresponding index').boolean('l')
  .alias('t', 'subtitles').describe('t', 'load subtitles file')
  .alias('q', 'quiet').describe('q', 'be quiet').boolean('v')
  .alias('v', 'vlc').describe('v', 'autoplay in vlc*').boolean('v')
  .alias('s', 'airplay').describe('s', 'autoplay via AirPlay').boolean('a')
  .alias('m', 'mplayer').describe('m', 'autoplay in mplayer*').boolean('m')
  .alias('k', 'mpv').describe('k', 'autoplay in mpv*').boolean('k')
  .alias('o', 'omx').describe('o', 'autoplay in omx**').boolean('o')
  .alias('w', 'webplay').describe('w', 'autoplay in webplay').boolean('w')
  .alias('j', 'jack').describe('j', 'autoplay in omx** using the audio jack').boolean('j')
  .alias('f', 'path').describe('f', 'change buffer file path')
  .alias('b', 'blocklist').describe('b', 'use the specified blocklist')
  .alias('n', 'no-quit').describe('n', 'do not quit peerflix on vlc exit').boolean('n')
  .alias('a', 'all').describe('a', 'select all files in the torrent').boolean('a')
  .alias('r', 'remove').describe('r', 'remove files on exit').boolean('r')
  .alias('h', 'hostname').describe('h', 'host name or IP to bind the server to')
  .alias('e', 'peer').describe('e', 'add peer by ip:port')
  .alias('x', 'peer-port').describe('x', 'set peer listening port')
  .alias('d', 'not-on-top').describe('d', 'do not float video on top').boolean('d')
  .describe('on-downloaded', 'script to call when file is 100% downloaded')
  .describe('on-listening', 'script to call when server goes live')
  .describe('version', 'prints current version').boolean('boolean')
  .argv)

if (argv.version) {
  console.error(require('./package').version)
  process.exit(0)
}

var filename = argv._[0]
var onTop = !argv.d

if (!filename) {
  optimist.showHelp()
  console.error('Options passed after -- will be passed to your player')
  console.error('')
  console.error('  "peerflix magnet-link --vlc -- --fullscreen" will pass --fullscreen to vlc')
  console.error('')
  console.error('* Autoplay can take several seconds to start since it needs to wait for the first piece')
  console.error('** OMX player is the default Raspbian video player\n')
  process.exit(1)
}

var VLC_ARGS = '-q '+(onTop ? '--video-on-top' : '')+' --play-and-exit'
var OMX_EXEC = argv.jack ? 'omxplayer -r -o local ' : 'omxplayer -r -o hdmi '
var MPLAYER_EXEC = 'mplayer '+(onTop ? '-ontop' : '')+' -really-quiet -noidx -loop 0 '
var MPV_EXEC = 'mpv '+(onTop ? '--ontop' : '')+' --really-quiet --loop=no '

if (argv.t) {
  VLC_ARGS += ' --sub-file=' + argv.t
  OMX_EXEC += ' --subtitles ' + argv.t
  MPLAYER_EXEC += ' -sub ' + argv.t
  MPV_EXEC += ' --sub-file=' + argv.t
}

if (argv._.length > 1) {
  var _args = argv._
  _args.shift()
  var playerArgs = _args.join(' ')
  VLC_ARGS += ' ' + playerArgs
  OMX_EXEC += ' ' + playerArgs
  MPLAYER_EXEC += ' ' + playerArgs
  MPV_EXEC += ' ' + playerArgs
}

var noop = function() {}

var ontorrent = function(torrent) {
  if (argv['peer-port']) argv.peerPort = Number(argv['peer-port'])

  var engine = peerflix(torrent, argv)
  var hotswaps = 0
  var verified = 0
  var invalid = 0

  engine.on('verify', function() {
    verified++
  })

  engine.on('invalid-piece', function() {
    invalid++
  })

  var bytes = function(num) {
    return numeral(num).format('0.0b')
  }

  if (argv.list) {
    var onready = function() {
      engine.files.forEach(function(file, i, files) {
        clivas.line('{3+bold:'+i+'} : {magenta:'+file.name+'} : {blue:'+bytes(file.length)+'}')
      })
      process.exit(0)
    }
    if (engine.torrent) onready()
    else engine.on('ready', onready)
    return
  }

  engine.on('hotswap', function() {
    hotswaps++
  })

  var started = Date.now()
  var wires = engine.swarm.wires
  var swarm = engine.swarm

  var active = function(wire) {
    return !wire.peerChoking
  }

  var peers = [].concat(argv.peer || [])
  peers.forEach(function(peer) {
    engine.connect(peer)
  })

  if (argv['on-downloaded']){
    var downloaded = false
    engine.on('uninterested', function() {
      if(!downloaded) proc.exec(argv['on-downloaded'])
      downloaded = true
    })
  }

  engine.server.on('listening', function() {
    var host = argv.hostname || address()
    var href = 'http://'+host+':'+engine.server.address().port+'/'
    var localHref = 'http://localhost:'+engine.server.address().port+'/'
    var filename = engine.server.index.name.split('/').pop().replace(/\{|\}/g, '')
    var filelength = engine.server.index.length
    var player = null
    var paused = false
    var timePaused = 0
    var pausedAt = null

    if (argv.all) {
      filename = engine.torrent.name
      filelength = engine.torrent.length
      href += '.m3u'
      localHref += '.m3u'
    }

    if (argv.vlc && process.platform === 'win32') {
      player = 'vlc'
      var registry = require('windows-no-runnable').registry
      var key
      if (process.arch === 'x64') {
        try {
          key = registry('HKLM/Software/Wow6432Node/VideoLAN/VLC')
        } catch (e) {
          try {
            key = registry('HKLM/Software/VideoLAN/VLC')
          } catch (err) {}
        }
      } else {
        try {
          key = registry('HKLM/Software/VideoLAN/VLC')
        } catch (err) {
          try {
            key = registry('HKLM/Software/Wow6432Node/VideoLAN/VLC')
          } catch (e) {}
        }
      }

      if (key) {
        var vlcPath = key['InstallDir'].value + path.sep + 'vlc'
        VLC_ARGS = VLC_ARGS.split(' ')
        VLC_ARGS.unshift(localHref)
        proc.execFile(vlcPath, VLC_ARGS)
      }
    } else {
      if (argv.vlc) {
        player = 'vlc'
        var root = '/Applications/VLC.app/Contents/MacOS/VLC'
        var home = (process.env.HOME || '') + root
        var vlc = proc.exec('vlc '+VLC_ARGS+' '+localHref+' || '+root+' '+VLC_ARGS+' '+localHref+' || '+home+' '+VLC_ARGS+' '+localHref, function(error, stdout, stderror){
          if (error) {
            process.exit(0)
          }
        })

        vlc.on('exit', function(){
          if (!argv.n && argv.quit !== false) process.exit(0)
        })
      }
    }

    if (argv.omx) {
      player = 'omx'
      proc.exec(OMX_EXEC+' '+localHref)
    }
    if (argv.mplayer) {
      player = 'mplayer'
      proc.exec(MPLAYER_EXEC+' '+localHref)
    }
    if (argv.mpv) {
      player = 'mpv'
      proc.exec(MPV_EXEC+' '+localHref)
    }
    if (argv.webplay) {
      player = 'webplay'
      open('https://85d514b3e548d934d8ff7c45a54732e65a3162fe.htmlb.in/#'+localHref)
    }
    if (argv.airplay) {
      var browser = require('airplay-js').createBrowser()
      browser.on('deviceOn', function( device ) {
        device.play(href, 0, noop)
      })
      browser.start()
    }

    if (argv['on-listening']) proc.exec(argv['on-listening']+' '+href)

    if (argv.quiet) return console.log('server is listening on '+href)

    process.stdout.write(new Buffer('G1tIG1sySg==', 'base64')) // clear for drawing

    var interactive = !player && process.stdin.isTTY && !!process.stdin.setRawMode

    if (interactive) {
      keypress(process.stdin)
      process.stdin.on('keypress', function(ch, key) {
        if (!key) return
        if (key.name === 'c' && key.ctrl === true) return process.kill(process.pid, 'SIGINT')
        if (key.name !== 'space') return

        if (player) return
        if (paused === false) {
          if(!argv.all) {
            engine.server.index.deselect()
          } else {
            engine.files.forEach(function(file) {
              file.deselect()
            })
          }
          paused = true
          pausedAt = Date.now()
          draw()
          return
        }

        if (!argv.all) {
          engine.server.index.select()
        } else {
          engine.files.forEach(function(file) {
            file.select()
          })
        }

        paused = false
        timePaused += Date.now() - pausedAt
        draw()
      })
      process.stdin.setRawMode(true)
    }

    var draw = function() {
      var unchoked = engine.swarm.wires.filter(active)
      var timeCurrentPause = 0
      if (paused === true) {
        timeCurrentPause = Date.now() - pausedAt
      }
      var runtime = Math.floor((Date.now() - started - timePaused - timeCurrentPause) / 1000)
      var linesremaining = clivas.height
      var peerslisted = 0

      clivas.clear()
      if (argv.airplay) clivas.line('{green:streaming to} {bold:apple-tv} {green:using airplay}')
      else clivas.line('{green:open} {bold:'+(player || 'vlc')+'} {green:and enter} {bold:'+href+'} {green:as the network address}')
      clivas.line('')
      clivas.line('{yellow:info} {green:streaming} {bold:'+filename+' ('+bytes(filelength)+')} {green:-} {bold:'+bytes(swarm.downloadSpeed())+'/s} {green:from} {bold:'+unchoked.length +'/'+wires.length+'} {green:peers}    ')
      clivas.line('{yellow:info} {green:path} {cyan:' + engine.path + '}')
      clivas.line('{yellow:info} {green:downloaded} {bold:'+bytes(swarm.downloaded)+'} {green:and uploaded }{bold:'+bytes(swarm.uploaded)+'} {green:in }{bold:'+runtime+'s} {green:with} {bold:'+hotswaps+'} {green:hotswaps}     ')
      clivas.line('{yellow:info} {green:verified} {bold:'+verified+'} {green:pieces and received} {bold:'+invalid+'} {green:invalid pieces}')
      clivas.line('{yellow:info} {green:peer queue size is} {bold:'+swarm.queued+'}')
      clivas.line('{80:}')

      if (interactive) {
        if (paused) clivas.line('{yellow:PAUSED} {green:Press SPACE to continue download}')
        else clivas.line('{50+green:Press SPACE to pause download}')
      }

      clivas.line('')
      linesremaining -= 9

      wires.every(function(wire) {
        var tags = []
        if (wire.peerChoking) tags.push('choked')
        clivas.line('{25+magenta:'+wire.peerAddress+'} {10:'+bytes(wire.downloaded)+'} {10+cyan:'+bytes(wire.downloadSpeed())+'/s} {15+grey:'+tags.join(', ')+'}   ')
        peerslisted++
        return linesremaining-peerslisted > 4
      })
      linesremaining -= peerslisted

      if (wires.length > peerslisted) {
        clivas.line('{80:}')
        clivas.line('... and '+(wires.length-peerslisted)+' more     ')
      }

      clivas.line('{80:}')
      clivas.flush()
    }

    setInterval(draw, 500)
    draw()
  })

  engine.server.once('error', function() {
    engine.server.listen(0, argv.hostname)
  })

  var onmagnet = function() {
    clivas.clear()
    clivas.line('{green:fetching torrent metadata from} {bold:'+engine.swarm.wires.length+'} {green:peers}')
  }

  if (typeof torrent === 'string' && torrent.indexOf('magnet:') === 0 && !argv.quiet) {
    onmagnet()
    engine.swarm.on('wire', onmagnet)
  }

  engine.on('ready', function() {
    engine.swarm.removeListener('wire', onmagnet)
    if (!argv.all) return
    engine.files.forEach(function(file) {
      file.select()
    })
  })

  var onexit = function() {
    // we're doing some heavy lifting so it can take some time to exit... let's
    // better output a status message so the user knows we're working on it :)
    clivas.line('')
    clivas.line('{yellow:info} {green:peerflix is exiting...}')
  }

  if(argv.remove) {
    var remove = function() {
      onexit()
      engine.remove(function() {
        process.exit()
      })
    }

    process.on('SIGINT', remove)
    process.on('SIGTERM', remove)
  } else {
    process.on('SIGINT', function() {
      onexit()
      process.exit()
    })
  }
}

if (/^magnet:/.test(filename)) return ontorrent(filename)

// TODO: don't use read-torrent anymore as we don't really use the parsing part of it...
readTorrent(filename, function(err, torrent, raw) {
  if (err) {
    console.error(err.message)
    process.exit(1)
  }

  ontorrent(raw) // use raw so we don't get infohash/metadata issues in torrent-stream.
})
<commit_msg>Added SMplayer support<commit_after>#!/usr/bin/env node

var optimist = require('optimist')
var rc = require('rc')
var clivas = require('clivas')
var numeral = require('numeral')
var os = require('os')
var address = require('network-address')
var readTorrent = require('read-torrent')
var proc = require('child_process')
var peerflix = require('./')
var keypress = require('keypress')
var open = require('open')

var path = require('path')

process.title = 'peerflix'

var argv = rc('peerflix', {}, optimist
  .usage('Usage: $0 magnet-link-or-torrent [options]')
  .alias('c', 'connections').describe('c', 'max connected peers').default('c', os.cpus().length > 1 ? 100 : 30)
  .alias('p', 'port').describe('p', 'change the http port').default('p', 8888)
  .alias('i', 'index').describe('i', 'changed streamed file (index)')
  .alias('l', 'list').describe('l', 'list available files with corresponding index').boolean('l')
  .alias('t', 'subtitles').describe('t', 'load subtitles file')
  .alias('q', 'quiet').describe('q', 'be quiet').boolean('v')
  .alias('v', 'vlc').describe('v', 'autoplay in vlc*').boolean('v')
  .alias('s', 'airplay').describe('s', 'autoplay via AirPlay').boolean('a')
  .alias('m', 'mplayer').describe('m', 'autoplay in mplayer*').boolean('m')
  .alias('g', 'smplayer').describe('g', 'autoplay in smplayer*').boolean('g')
  .alias('k', 'mpv').describe('k', 'autoplay in mpv*').boolean('k')
  .alias('o', 'omx').describe('o', 'autoplay in omx**').boolean('o')
  .alias('w', 'webplay').describe('w', 'autoplay in webplay').boolean('w')
  .alias('j', 'jack').describe('j', 'autoplay in omx** using the audio jack').boolean('j')
  .alias('f', 'path').describe('f', 'change buffer file path')
  .alias('b', 'blocklist').describe('b', 'use the specified blocklist')
  .alias('n', 'no-quit').describe('n', 'do not quit peerflix on vlc exit').boolean('n')
  .alias('a', 'all').describe('a', 'select all files in the torrent').boolean('a')
  .alias('r', 'remove').describe('r', 'remove files on exit').boolean('r')
  .alias('h', 'hostname').describe('h', 'host name or IP to bind the server to')
  .alias('e', 'peer').describe('e', 'add peer by ip:port')
  .alias('x', 'peer-port').describe('x', 'set peer listening port')
  .alias('d', 'not-on-top').describe('d', 'do not float video on top').boolean('d')
  .describe('on-downloaded', 'script to call when file is 100% downloaded')
  .describe('on-listening', 'script to call when server goes live')
  .describe('version', 'prints current version').boolean('boolean')
  .argv)

if (argv.version) {
  console.error(require('./package').version)
  process.exit(0)
}

var filename = argv._[0]
var onTop = !argv.d

if (!filename) {
  optimist.showHelp()
  console.error('Options passed after -- will be passed to your player')
  console.error('')
  console.error('  "peerflix magnet-link --vlc -- --fullscreen" will pass --fullscreen to vlc')
  console.error('')
  console.error('* Autoplay can take several seconds to start since it needs to wait for the first piece')
  console.error('** OMX player is the default Raspbian video player\n')
  process.exit(1)
}

var VLC_ARGS = '-q '+(onTop ? '--video-on-top' : '')+' --play-and-exit'
var OMX_EXEC = argv.jack ? 'omxplayer -r -o local ' : 'omxplayer -r -o hdmi '
var MPLAYER_EXEC = 'mplayer '+(onTop ? '-ontop' : '')+' -really-quiet -noidx -loop 0 '
var SMPLAYER_EXEC = 'smplayer '+(onTop ? '-ontop' : '')
var MPV_EXEC = 'mpv '+(onTop ? '--ontop' : '')+' --really-quiet --loop=no '

if (argv.t) {
  VLC_ARGS += ' --sub-file=' + argv.t
  OMX_EXEC += ' --subtitles ' + argv.t
  MPLAYER_EXEC += ' -sub ' + argv.t
  SMPLAYER_EXEC += ' -sub ' + argv.t
  MPV_EXEC += ' --sub-file=' + argv.t
}

if (argv._.length > 1) {
  var _args = argv._
  _args.shift()
  var playerArgs = _args.join(' ')
  VLC_ARGS += ' ' + playerArgs
  OMX_EXEC += ' ' + playerArgs
  MPLAYER_EXEC += ' ' + playerArgs
  SMPLAYER_EXEC += ' ' + playerArgs;
  MPV_EXEC += ' ' + playerArgs
}

var noop = function() {}

var ontorrent = function(torrent) {
  if (argv['peer-port']) argv.peerPort = Number(argv['peer-port'])

  var engine = peerflix(torrent, argv)
  var hotswaps = 0
  var verified = 0
  var invalid = 0

  engine.on('verify', function() {
    verified++
  })

  engine.on('invalid-piece', function() {
    invalid++
  })

  var bytes = function(num) {
    return numeral(num).format('0.0b')
  }

  if (argv.list) {
    var onready = function() {
      engine.files.forEach(function(file, i, files) {
        clivas.line('{3+bold:'+i+'} : {magenta:'+file.name+'} : {blue:'+bytes(file.length)+'}')
      })
      process.exit(0)
    }
    if (engine.torrent) onready()
    else engine.on('ready', onready)
    return
  }

  engine.on('hotswap', function() {
    hotswaps++
  })

  var started = Date.now()
  var wires = engine.swarm.wires
  var swarm = engine.swarm

  var active = function(wire) {
    return !wire.peerChoking
  }

  var peers = [].concat(argv.peer || [])
  peers.forEach(function(peer) {
    engine.connect(peer)
  })

  if (argv['on-downloaded']){
    var downloaded = false
    engine.on('uninterested', function() {
      if(!downloaded) proc.exec(argv['on-downloaded'])
      downloaded = true
    })
  }

  engine.server.on('listening', function() {
    var host = argv.hostname || address()
    var href = 'http://'+host+':'+engine.server.address().port+'/'
    var localHref = 'http://localhost:'+engine.server.address().port+'/'
    var filename = engine.server.index.name.split('/').pop().replace(/\{|\}/g, '')
    var filelength = engine.server.index.length
    var player = null
    var paused = false
    var timePaused = 0
    var pausedAt = null

    if (argv.all) {
      filename = engine.torrent.name
      filelength = engine.torrent.length
      href += '.m3u'
      localHref += '.m3u'
    }

    if (argv.vlc && process.platform === 'win32') {
      player = 'vlc'
      var registry = require('windows-no-runnable').registry
      var key
      if (process.arch === 'x64') {
        try {
          key = registry('HKLM/Software/Wow6432Node/VideoLAN/VLC')
        } catch (e) {
          try {
            key = registry('HKLM/Software/VideoLAN/VLC')
          } catch (err) {}
        }
      } else {
        try {
          key = registry('HKLM/Software/VideoLAN/VLC')
        } catch (err) {
          try {
            key = registry('HKLM/Software/Wow6432Node/VideoLAN/VLC')
          } catch (e) {}
        }
      }

      if (key) {
        var vlcPath = key['InstallDir'].value + path.sep + 'vlc'
        VLC_ARGS = VLC_ARGS.split(' ')
        VLC_ARGS.unshift(localHref)
        proc.execFile(vlcPath, VLC_ARGS)
      }
    } else {
      if (argv.vlc) {
        player = 'vlc'
        var root = '/Applications/VLC.app/Contents/MacOS/VLC'
        var home = (process.env.HOME || '') + root
        var vlc = proc.exec('vlc '+VLC_ARGS+' '+localHref+' || '+root+' '+VLC_ARGS+' '+localHref+' || '+home+' '+VLC_ARGS+' '+localHref, function(error, stdout, stderror){
          if (error) {
            process.exit(0)
          }
        })

        vlc.on('exit', function(){
          if (!argv.n && argv.quit !== false) process.exit(0)
        })
      }
    }

    if (argv.omx) {
      player = 'omx'
      proc.exec(OMX_EXEC+' '+localHref)
    }
    if (argv.mplayer) {
      player = 'mplayer'
      proc.exec(MPLAYER_EXEC+' '+localHref)
    }
    if (argv.smplayer) {
      player = 'smplayer';
      proc.exec(SMPLAYER_EXEC+' '+localHref);
    }
    if (argv.mpv) {
      player = 'mpv'
      proc.exec(MPV_EXEC+' '+localHref)
    }
    if (argv.webplay) {
      player = 'webplay'
      open('https://85d514b3e548d934d8ff7c45a54732e65a3162fe.htmlb.in/#'+localHref)
    }
    if (argv.airplay) {
      var browser = require('airplay-js').createBrowser()
      browser.on('deviceOn', function( device ) {
        device.play(href, 0, noop)
      })
      browser.start()
    }

    if (argv['on-listening']) proc.exec(argv['on-listening']+' '+href)

    if (argv.quiet) return console.log('server is listening on '+href)

    process.stdout.write(new Buffer('G1tIG1sySg==', 'base64')) // clear for drawing

    var interactive = !player && process.stdin.isTTY && !!process.stdin.setRawMode

    if (interactive) {
      keypress(process.stdin)
      process.stdin.on('keypress', function(ch, key) {
        if (!key) return
        if (key.name === 'c' && key.ctrl === true) return process.kill(process.pid, 'SIGINT')
        if (key.name !== 'space') return

        if (player) return
        if (paused === false) {
          if(!argv.all) {
            engine.server.index.deselect()
          } else {
            engine.files.forEach(function(file) {
              file.deselect()
            })
          }
          paused = true
          pausedAt = Date.now()
          draw()
          return
        }

        if (!argv.all) {
          engine.server.index.select()
        } else {
          engine.files.forEach(function(file) {
            file.select()
          })
        }

        paused = false
        timePaused += Date.now() - pausedAt
        draw()
      })
      process.stdin.setRawMode(true)
    }

    var draw = function() {
      var unchoked = engine.swarm.wires.filter(active)
      var timeCurrentPause = 0
      if (paused === true) {
        timeCurrentPause = Date.now() - pausedAt
      }
      var runtime = Math.floor((Date.now() - started - timePaused - timeCurrentPause) / 1000)
      var linesremaining = clivas.height
      var peerslisted = 0

      clivas.clear()
      if (argv.airplay) clivas.line('{green:streaming to} {bold:apple-tv} {green:using airplay}')
      else clivas.line('{green:open} {bold:'+(player || 'vlc')+'} {green:and enter} {bold:'+href+'} {green:as the network address}')
      clivas.line('')
      clivas.line('{yellow:info} {green:streaming} {bold:'+filename+' ('+bytes(filelength)+')} {green:-} {bold:'+bytes(swarm.downloadSpeed())+'/s} {green:from} {bold:'+unchoked.length +'/'+wires.length+'} {green:peers}    ')
      clivas.line('{yellow:info} {green:path} {cyan:' + engine.path + '}')
      clivas.line('{yellow:info} {green:downloaded} {bold:'+bytes(swarm.downloaded)+'} {green:and uploaded }{bold:'+bytes(swarm.uploaded)+'} {green:in }{bold:'+runtime+'s} {green:with} {bold:'+hotswaps+'} {green:hotswaps}     ')
      clivas.line('{yellow:info} {green:verified} {bold:'+verified+'} {green:pieces and received} {bold:'+invalid+'} {green:invalid pieces}')
      clivas.line('{yellow:info} {green:peer queue size is} {bold:'+swarm.queued+'}')
      clivas.line('{80:}')

      if (interactive) {
        if (paused) clivas.line('{yellow:PAUSED} {green:Press SPACE to continue download}')
        else clivas.line('{50+green:Press SPACE to pause download}')
      }

      clivas.line('')
      linesremaining -= 9

      wires.every(function(wire) {
        var tags = []
        if (wire.peerChoking) tags.push('choked')
        clivas.line('{25+magenta:'+wire.peerAddress+'} {10:'+bytes(wire.downloaded)+'} {10+cyan:'+bytes(wire.downloadSpeed())+'/s} {15+grey:'+tags.join(', ')+'}   ')
        peerslisted++
        return linesremaining-peerslisted > 4
      })
      linesremaining -= peerslisted

      if (wires.length > peerslisted) {
        clivas.line('{80:}')
        clivas.line('... and '+(wires.length-peerslisted)+' more     ')
      }

      clivas.line('{80:}')
      clivas.flush()
    }

    setInterval(draw, 500)
    draw()
  })

  engine.server.once('error', function() {
    engine.server.listen(0, argv.hostname)
  })

  var onmagnet = function() {
    clivas.clear()
    clivas.line('{green:fetching torrent metadata from} {bold:'+engine.swarm.wires.length+'} {green:peers}')
  }

  if (typeof torrent === 'string' && torrent.indexOf('magnet:') === 0 && !argv.quiet) {
    onmagnet()
    engine.swarm.on('wire', onmagnet)
  }

  engine.on('ready', function() {
    engine.swarm.removeListener('wire', onmagnet)
    if (!argv.all) return
    engine.files.forEach(function(file) {
      file.select()
    })
  })

  var onexit = function() {
    // we're doing some heavy lifting so it can take some time to exit... let's
    // better output a status message so the user knows we're working on it :)
    clivas.line('')
    clivas.line('{yellow:info} {green:peerflix is exiting...}')
  }

  if(argv.remove) {
    var remove = function() {
      onexit()
      engine.remove(function() {
        process.exit()
      })
    }

    process.on('SIGINT', remove)
    process.on('SIGTERM', remove)
  } else {
    process.on('SIGINT', function() {
      onexit()
      process.exit()
    })
  }
}

if (/^magnet:/.test(filename)) return ontorrent(filename)

// TODO: don't use read-torrent anymore as we don't really use the parsing part of it...
readTorrent(filename, function(err, torrent, raw) {
  if (err) {
    console.error(err.message)
    process.exit(1)
  }

  ontorrent(raw) // use raw so we don't get infohash/metadata issues in torrent-stream.
})
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux';
import { Row, Col } from 'react-flexbox-grid/lib/index';
import { align, cardSpace } from 'lib/styles';
import FlatButton from 'material-ui/FlatButton';
import FontIcon from 'material-ui/FontIcon';
import { Card, CardHeader } from 'material-ui/Card';

// #footer {
//   padding: 72px 24px 72px 24px;
//   box-sizing: border-box;
//   background-color: #333;
//   color: #92c1ff;
//   text-align: center;
//
//   a {
//     color: #89a8ff;
//   }
// }

const Render = () => {
    const styles = {
        footerDiv: {
            paddingTop: '40px',
            paddingBottom: '40px',
            cardSpace,
            // backgroundColor: 'dimgray',
            margin: '5px',
        },
    };

    return (
        <Row style={{...styles.footerDiv}}>
            <Col xs={2} style={{color: 'limegreen'}}>
                Beta 0.0.1
            </Col>
            <Col xs={8} style={{color: 'gray', fontWeight: '300'}}>

                Find an issue? Got a suggestion?

                Please let us know on our
                Github issues page.

                Made with ❤  by ETCDEV and many wonderful contributors.

            </Col>
            <Col xs={2} style={align.right}>

                <FlatButton label="Source"
                    labelPosition="before"
                    href="https://github.com/ethereumproject/emerald-wallet"
                    icon={<FontIcon className="fa fa-github" />}/>
            </Col>
        </Row>
    );
};

const Footer = connect(
    (state, ownProps) => ({}),
    (dispatch, ownProps) => ({})
)(Render);

export default Footer;
<commit_msg>problem: targets cant be blank for sending links to browser<commit_after>import React from 'react';
import { connect } from 'react-redux';
import { Row, Col } from 'react-flexbox-grid/lib/index';
import { align, cardSpace } from 'lib/styles';
import FlatButton from 'material-ui/FlatButton';
import FontIcon from 'material-ui/FontIcon';
import { Card, CardHeader } from 'material-ui/Card';

// #footer {
//   padding: 72px 24px 72px 24px;
//   box-sizing: border-box;
//   background-color: #333;
//   color: #92c1ff;
//   text-align: center;
//
//   a {
//     color: #89a8ff;
//   }
// }

const Render = () => {
    const styles = {
        footerDiv: {
            paddingTop: '40px',
            paddingBottom: '40px',
            cardSpace,
            // backgroundColor: 'dimgray',
            margin: '5px',
        },
    };

    return (
        <Row style={{...styles.footerDiv}}>
            <Col xs={2} style={{color: 'limegreen'}}>
                Beta 0.0.1
            </Col>
            <Col xs={8} style={{color: 'gray', fontWeight: '300'}}>

                Find an issue? Got a suggestion?

                Please let us know on our
                Github issues page.

                Made with ❤  by ETCDEV and many wonderful contributors.

            </Col>
            <Col xs={2} style={align.right}>

                <FlatButton label="Source"
                    labelPosition="before"
                    href="https://github.com/ethereumproject/emerald-wallet"
                    icon={<FontIcon className="fa fa-github" />}/>
            </Col>
        </Row>
    );
};

const Footer = connect(
    (state, ownProps) => ({}),
    (dispatch, ownProps) => ({})
)(Render);

export default Footer;
<|endoftext|>


<commit_before>var Users 	= require("../models/users");
var Chat  	= require("../models/chat");

module.exports = {

	recipient: {
		auth: {
			mode: "optional"
		},
		handler: function (request, reply) {

			var profile = request.auth.credentials;
			var myid = profile.id;
			var chatWith = Object.keys(request.payload)[0];
			var users = { "users": [myid, chatWith] };

			profile.talkingTo = chatWith;
			request.auth.session.set(profile);

			Chat.findChat(myid, chatWith, function (err, data) {
				if (data.length === 0) {

					console.log("New chat");
					Chat.newChat(users, function(err, data){
						reply.redirect("/chat/" + chatWith);
					});

				}
				else {
					console.log("Chat exists");
					reply.redirect("/chat/" + chatWith);
				}
			});

		}
	},

	view: {
		auth: {
			mode: "optional"
		},
		handler: function (request, reply) {
			var profile = request.auth.credentials;
			var myid = profile.id;
			var chatWith = profile.talkingTo;

			Chat.findChat(myid, chatWith, function (err, data) {
				console.log(data[0]);

				Users.findUser(chatWith, function (err, result){
					console.log(result);
					reply.view("chat", {
						data: data[0],
						profile: profile,
						talkingTo: result
					});
				});
			});
		}
	},

	submit: {
		auth: {
			mode: "optional"
		},
		handler: function (request, reply) {

			var profile = request.auth.credentials;
			var myid = profile.id;
			var chatWith = profile.talkingTo;
			var d = new Date();

			var chat = { $push: { "chat": {
				"sender": myid,
				"name": profile.firstName + " " + profile.lastName,
				"message": request.payload.chat,
				"time": d.toUTCString()
			}}};

			Chat.addChat(myid, chatWith, chat, function (err, data) {
				reply.redirect("/chat/"+ profile.talkingTo);
			});
		}
	}

};<commit_msg>style<commit_after>var Users = require("../models/users");
var Chat = require("../models/chat");

module.exports = {

	recipient: {
		auth: {
			mode: "optional"
		},
		handler: function (request, reply) {

			var profile = request.auth.credentials;
			var myid = profile.id;
			var chatWith = Object.keys(request.payload)[0];
			var users = { "users": [myid, chatWith] };

			profile.talkingTo = chatWith;
			request.auth.session.set(profile);

			Chat.findChat(myid, chatWith, function (err, data) {
				if (data.length === 0) {

					console.log("New chat");
					Chat.newChat(users, function(err, data){
						reply.redirect("/chat/" + chatWith);
					});

				}
				else {
					console.log("Chat exists");
					reply.redirect("/chat/" + chatWith);
				}
			});

		}
	},

	view: {
		auth: {
			mode: "optional"
		},
		handler: function (request, reply) {
			var profile = request.auth.credentials;
			var myid = profile.id;
			var chatWith = profile.talkingTo;

			Chat.findChat(myid, chatWith, function (err, data) {
				console.log(data[0]);

				Users.findUser(chatWith, function (err, result){
					console.log(result);
					reply.view("chat", {
						data: data[0],
						profile: profile,
						talkingTo: result
					});
				});
			});
		}
	},

	submit: {
		auth: {
			mode: "optional"
		},
		handler: function (request, reply) {

			var profile = request.auth.credentials;
			var myid = profile.id;
			var chatWith = profile.talkingTo;
			var d = new Date();

			var chat = { $push: { "chat": {
				"sender": myid,
				"name": profile.firstName + " " + profile.lastName,
				"message": request.payload.chat,
				"time": d.toUTCString()
			}}};

			Chat.addChat(myid, chatWith, chat, function (err, data) {
				reply.redirect("/chat/"+ profile.talkingTo);
			});
		}
	}

};<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var gDebug, gLog;
var gAnalysisPath = "../../";
var gBzAPIPath = "https://bugzilla.mozilla.org/bzapi/";
var gBzBasePath = "https://bugzilla.mozilla.org/";
var gSocorroPath = "https://crash-stats.mozilla.com/";

// Should select / figure out from https://crash-stats.mozilla.com/api/ProductsVersions/ or https://crash-stats.mozilla.com/api/CurrentVersions/
var gProduct = "Firefox", gVersion = "38.0b1", gProcess = "browser";
var gDate, gDuration = 7;
var gScores = {}, gSocorroAPIToken;

window.onload = function() {
  gDebug = document.getElementById("debug");
  gLog = document.getElementById("debugLog");
  gSocorroAPIToken = getParameterByName("token");

  var ver = getParameterByName("version");
  if (ver.match(/^(\d+\.)+[\dab]+/)) {
    gVersion = ver;
  }

  //if (!gSocorroAPIToken) {
  //  $err = displayMessage("ERROR - you need an API token. Please ");
  //  $link = $err.appendChild(document.createElement("a"));
  //  $link.setAttribute("href", "https://crash-stats.mozilla.com/api/tokens/");
  //  $link.textContent = "create one via Socorro";
  //  $err.appendChild(document.createTextNode(" and hand it over with the ?token=... parameter!"));
  //}
  //else {
    fetchFile(gSocorroPath + "api/CrontabberState/", "json",
      function(aData) {
        if (aData) {
          var matviewTime = new Date(aData.state["signatures-matview"].last_success);
          matviewTime.setDate(matviewTime.getDate() - 1); // subtract one day
          gDate = makeDate(matviewTime);
          document.getElementById("repDate").textContent = gDate;
          document.getElementById("repDuration").textContent = gDuration;
          document.getElementById("repProd").textContent = gProduct;
          document.getElementById("repVer").textContent = gVersion;
          document.getElementById("repPType").textContent = gProcess;

          processData();
        }
        else {
          gDate = null;
          displayMessage("ERROR - couldn't find crobtabber state!");
        }
      }
    );
  //}
}

function processData() {
  var tblBody = document.getElementById("scoreTBody");
  var limit = 10; //300;
  fetchFile(gSocorroPath + "api/TCBS/?product=" + gProduct + "&version=" + gVersion +
            "&crash_type=" + gProcess + "&date_range_type=" + gDuration +
            "&end_date=" + gDate + "&limit=" + limit, "json",
    function(aData) {
      if (aData) {
        var resultCount = aData.crashes.length;
        displayMessage("Processing " + resultCount + " results…");
        for (var i = 0; i <= resultCount - 1; i++) {
          gScores[aData.crashes[i].signature] = aData.crashes[i];
          calcScore(aData.crashes[i].signature, function(aSignature) {
            if (aSignature == aData.crashes[resultCount-1].signature) {
              // last item, so all done with calculating
              buildDataTable();
            }
          });
        }
      }
      else {
        displayMessage("ERROR - couldn't find TCBS data!");
      }
    }
  );
}

function buildDataTable() {
  var msgRow = document.getElementById("message_row");
  if (msgRow) {
    msgRow.parentNode.removeChild(msgRow);
  }
  // Header
  var trow = document.getElementById("scoreTHeader")
                     .appendChild(document.createElement("tr"));
  var cell = trow.appendChild(document.createElement("th"));
  cell.textContent = "Signature";
  var cell = trow.appendChild(document.createElement("th"));
  cell.textContent = "Count";
  var cell = trow.appendChild(document.createElement("th"));
  cell.textContent = "Score";
  // Body
  var tblBody = document.getElementById("scoreTBody");
  var sigSorted = Object.keys(gScores).sort(
    function (a, b) { return gScores[b].score - gScores[a].score; }
  );
  for (var i = 0; i <= sigSorted.length - 1; i++) {
    signature = sigSorted[i];
    var trow = tblBody.appendChild(document.createElement("tr"));
    trow.setAttribute("id", "sdata_" + encodeURIComponent(signature));
    var cell = trow.appendChild(document.createElement("td"));
    cell.classList.add("sig");
    var link = cell.appendChild(document.createElement("a"));
    link.setAttribute("href",
        gSocorroPath + "report/list?product=" + gProduct +
        "&version=" + gProduct + ":" + gVersion +
        "&range_unit=days&range_value=" + gDuration + "&date=" + gDate +
        "&signature=" + encodeURIComponent(signature));
    link.textContent = signature;
    var cell = trow.appendChild(document.createElement("td"));
    cell.classList.add("num");
    cell.textContent = gScores[signature].count;
    var cell = trow.appendChild(document.createElement("td"));
    cell.classList.add("num");
    cell.textContent = parseInt(gScores[signature].score);
  }
}

function calcScore(aSignature, aCallback) {
  gScores[aSignature].score = gScores[aSignature].count;
  // Startup crashes: count each crash with factor 10
  gScores[aSignature].score *= 1 + gScores[aSignature].startup_percent * (10 - 1);
  // GC crashes: count each crash with factor 1/4
  gScores[aSignature].score *= 1 + (gScores[aSignature].is_gc_count * (.25 - 1)) / gScores[aSignature].count;
  // OOM | small: factor 1/10
  if (aSignature == "OOM | small") {
    gScores[aSignature].score *= .1;
  }
  // OOM | large: factor 5
  if (aSignature.startsWith("OOM | large")) {
    gScores[aSignature].score *= 5;
  }

  // Get signature Summary data
  var sigRepTypes = "distinct_install";
  var startDate = new Date(gDate);
  startDate.setDate(startDate.getDate() - gDuration);
  fetchFile(gSocorroPath + "api/SignatureSummary/?versions=" + gProduct + ":" + gVersion +
            "&signature=" + encodeURIComponent(aSignature) +
            "&report_types=" + sigRepTypes +
            "&start_date=" + makeDate(startDate) + "&end_date=" + gDate, "json",
    function(aData) {
      if (aData) {
        gScores[aSignature].installations = aData.reports.distinct_install[0].installations;
        // installations: factor 0 for <3 installs
        if (aData.reports.distinct_install[0].installations < 3) {
          gScores[aSignature].score *= 0;
        }
        // installations: factor up to 2 for few people crashing over and over,
        //                factor 1 for installations == crashes
        // 1+e^(x*-3)*sin(x*pi)*3 - prototyped via http://www.mathe-fa.de/en
        var instRatio = aData.reports.distinct_install[0].installations /
                        aData.reports.distinct_install[0].crashes;
        gScores[aSignature].score *= 1 + 3 * Math.sin(instRatio * Math.PI) * Math.exp(instRatio * -3);
        gScores[aSignature].installations_factor = 1 + 3 * Math.sin(instRatio * Math.PI) * Math.exp(instRatio * -3);
        gScores[aSignature].installations_ratio = instRatio;
      }
      else {
        console.log("ERROR - couldn't find Signature Summary data for " + aSignature + "!");
      }
      aCallback(aSignature);
    }
  );
}

function displayMessage(aErrorMessage) {
  var trow = document.getElementById("scoreTBody")
                     .appendChild(document.createElement('tr'));
  trow.setAttribute("id", "message_row");
  var cell = trow.appendChild(document.createElement('td'));
  cell.textContent = aErrorMessage;
  return cell;
}

function fetchFile(aURL, aFormat, aCallback) {
  var XHR = new XMLHttpRequest();
  XHR.onreadystatechange = function() {
    if (XHR.readyState == 4) {/*
      gLog.appendChild(document.createElement("li"))
          .appendChild(document.createTextNode(aURL + " - " + XHR.status +
                                               " " + XHR.statusText));*/
    }
    if (XHR.readyState == 4 && XHR.status == 200) {
      // so far so good
      if (XHR.responseXML != null && aFormat == "xml" &&
          XHR.responseXML.getElementById('test').firstChild.data)
        aCallback(aXHR.responseXML.getElementById('test').firstChild.data);
      else if (XHR.responseText != null && aFormat == "json")
        aCallback(JSON.parse(XHR.responseText));
      else
        aCallback(XHR.responseText);
    } else if (XHR.readyState == 4 && XHR.status != 200) {
      // fetched the wrong page or network error...
      console.log("ERROR: XHR status " + XHR.status + " - " + aURL);
      aCallback(null);
    }
  };
  XHR.open("GET", aURL);
  if (gSocorroAPIToken) {
    // XXX: Should work but doesn't yet! We'll need to figure this out.
    //      Use this path when we have a token so bug 1143424 can be tested.
    XHR.setRequestHeader("Auth-Token", gSocorroAPIToken);
  }
  if (aFormat == "json") { XHR.setRequestHeader("Accept", "application/json"); }
  else if (aFormat == "xml") { XHR.setRequestHeader("Accept", "application/xml"); }
  try {
    XHR.send();
  }
  catch (e) {
    console.log("ERROR: XHR send - " + e + " - " + aURL);
    aCallback(null);
  }
}

function makeDate(aDate) {
  // Date format is YYYY-MM-DD
  // Note that .getMonth() returns a number between 0 and 11 (0 for January)!
  return aDate.getUTCFullYear() + "-" +
         (aDate.getUTCMonth() < 9 ? "0" : "") + (aDate.getUTCMonth() + 1 ) + "-" +
         (aDate.getUTCDate() < 10 ? "0" : "") + aDate.getUTCDate();
}

function getParameterByName(aName) {
  // from http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
  name = aName.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
  var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
      results = regex.exec(location.search);
  return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
<commit_msg>add a Rank column and make it possible to set a limit from parameters<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var gDebug, gLog;
var gAnalysisPath = "../../";
var gBzAPIPath = "https://bugzilla.mozilla.org/bzapi/";
var gBzBasePath = "https://bugzilla.mozilla.org/";
var gSocorroPath = "https://crash-stats.mozilla.com/";

// Should select / figure out from https://crash-stats.mozilla.com/api/ProductsVersions/ or https://crash-stats.mozilla.com/api/CurrentVersions/
var gProduct = "Firefox", gVersion = "38.0b1", gProcess = "browser", gLimit = 10; //300;
var gDate, gDuration = 7;
var gScores = {}, gSocorroAPIToken;

window.onload = function() {
  gDebug = document.getElementById("debug");
  gLog = document.getElementById("debugLog");
  gSocorroAPIToken = getParameterByName("token");

  var ver = getParameterByName("version");
  if (ver.match(/^(\d+\.)+[\dab]+/)) {
    gVersion = ver;
  }
  var limit = getParameterByName("limit");
  if (limit.match(/^(\d+)+/) && (limit >= 3) && (limit <= 1000)) {
    gLimit = limit;
  }

  //if (!gSocorroAPIToken) {
  //  $err = displayMessage("ERROR - you need an API token. Please ");
  //  $link = $err.appendChild(document.createElement("a"));
  //  $link.setAttribute("href", "https://crash-stats.mozilla.com/api/tokens/");
  //  $link.textContent = "create one via Socorro";
  //  $err.appendChild(document.createTextNode(" and hand it over with the ?token=... parameter!"));
  //}
  //else {
    fetchFile(gSocorroPath + "api/CrontabberState/", "json",
      function(aData) {
        if (aData) {
          var matviewTime = new Date(aData.state["signatures-matview"].last_success);
          matviewTime.setDate(matviewTime.getDate() - 1); // subtract one day
          gDate = makeDate(matviewTime);
          document.getElementById("repDate").textContent = gDate;
          document.getElementById("repDuration").textContent = gDuration;
          document.getElementById("repProd").textContent = gProduct;
          document.getElementById("repVer").textContent = gVersion;
          document.getElementById("repPType").textContent = gProcess;

          processData();
        }
        else {
          gDate = null;
          displayMessage("ERROR - couldn't find crobtabber state!");
        }
      }
    );
  //}
}

function processData() {
  var tblBody = document.getElementById("scoreTBody");
  fetchFile(gSocorroPath + "api/TCBS/?product=" + gProduct + "&version=" + gVersion +
            "&crash_type=" + gProcess + "&date_range_type=" + gDuration +
            "&end_date=" + gDate + "&limit=" + gLimit, "json",
    function(aData) {
      if (aData) {
        var resultCount = aData.crashes.length;
        displayMessage("Processing " + resultCount + " results…");
        for (var i = 0; i <= resultCount - 1; i++) {
          gScores[aData.crashes[i].signature] = aData.crashes[i];
          calcScore(aData.crashes[i].signature, function(aSignature) {
            if (aSignature == aData.crashes[resultCount-1].signature) {
              // last item, so all done with calculating
              buildDataTable();
            }
          });
        }
      }
      else {
        displayMessage("ERROR - couldn't find TCBS data!");
      }
    }
  );
}

function buildDataTable() {
  var msgRow = document.getElementById("message_row");
  if (msgRow) {
    msgRow.parentNode.removeChild(msgRow);
  }
  // Header
  var trow = document.getElementById("scoreTHeader")
                     .appendChild(document.createElement("tr"));
  var cell = trow.appendChild(document.createElement("th"));
  cell.textContent = "#";
  cell.setAttribute("title", "Rank");
  var cell = trow.appendChild(document.createElement("th"));
  cell.textContent = "Signature";
  var cell = trow.appendChild(document.createElement("th"));
  cell.textContent = "Count";
  var cell = trow.appendChild(document.createElement("th"));
  cell.textContent = "Score";
  // Body
  var tblBody = document.getElementById("scoreTBody");
  var sigSorted = Object.keys(gScores).sort(
    function (a, b) { return gScores[b].score - gScores[a].score; }
  );
  for (var i = 0; i <= sigSorted.length - 1; i++) {
    signature = sigSorted[i];
    var trow = tblBody.appendChild(document.createElement("tr"));
    trow.setAttribute("id", "sdata_" + encodeURIComponent(signature));
    var cell = trow.appendChild(document.createElement("td"));
    cell.textContent = i + 1;
    cell.classList.add("rank");
    var cell = trow.appendChild(document.createElement("td"));
    cell.classList.add("sig");
    var link = cell.appendChild(document.createElement("a"));
    link.setAttribute("href",
        gSocorroPath + "report/list?product=" + gProduct +
        "&version=" + gProduct + ":" + gVersion +
        "&range_unit=days&range_value=" + gDuration + "&date=" + gDate +
        "&signature=" + encodeURIComponent(signature));
    link.textContent = signature;
    var cell = trow.appendChild(document.createElement("td"));
    cell.classList.add("num");
    cell.textContent = gScores[signature].count;
    var cell = trow.appendChild(document.createElement("td"));
    cell.classList.add("num");
    cell.textContent = parseInt(gScores[signature].score);
  }
}

function calcScore(aSignature, aCallback) {
  gScores[aSignature].score = gScores[aSignature].count;
  // Startup crashes: count each crash with factor 10
  gScores[aSignature].score *= 1 + gScores[aSignature].startup_percent * (10 - 1);
  // GC crashes: count each crash with factor 1/4
  gScores[aSignature].score *= 1 + (gScores[aSignature].is_gc_count * (.25 - 1)) / gScores[aSignature].count;
  // OOM | small: factor 1/10
  if (aSignature == "OOM | small") {
    gScores[aSignature].score *= .1;
  }
  // OOM | large: factor 5
  if (aSignature.startsWith("OOM | large")) {
    gScores[aSignature].score *= 5;
  }

  // Get signature Summary data
  var sigRepTypes = "distinct_install";
  var startDate = new Date(gDate);
  startDate.setDate(startDate.getDate() - gDuration);
  fetchFile(gSocorroPath + "api/SignatureSummary/?versions=" + gProduct + ":" + gVersion +
            "&signature=" + encodeURIComponent(aSignature) +
            "&report_types=" + sigRepTypes +
            "&start_date=" + makeDate(startDate) + "&end_date=" + gDate, "json",
    function(aData) {
      if (aData) {
        gScores[aSignature].installations = aData.reports.distinct_install[0].installations;
        // installations: factor 0 for <3 installs
        if (aData.reports.distinct_install[0].installations < 3) {
          gScores[aSignature].score *= 0;
        }
        // installations: factor up to 2 for few people crashing over and over,
        //                factor 1 for installations == crashes
        // 1+e^(x*-3)*sin(x*pi)*3 - prototyped via http://www.mathe-fa.de/en
        var instRatio = aData.reports.distinct_install[0].installations /
                        aData.reports.distinct_install[0].crashes;
        gScores[aSignature].score *= 1 + 3 * Math.sin(instRatio * Math.PI) * Math.exp(instRatio * -3);
        gScores[aSignature].installations_factor = 1 + 3 * Math.sin(instRatio * Math.PI) * Math.exp(instRatio * -3);
        gScores[aSignature].installations_ratio = instRatio;
      }
      else {
        console.log("ERROR - couldn't find Signature Summary data for " + aSignature + "!");
      }
      aCallback(aSignature);
    }
  );
}

function displayMessage(aErrorMessage) {
  var trow = document.getElementById("scoreTBody")
                     .appendChild(document.createElement('tr'));
  trow.setAttribute("id", "message_row");
  var cell = trow.appendChild(document.createElement('td'));
  cell.textContent = aErrorMessage;
  return cell;
}

function fetchFile(aURL, aFormat, aCallback) {
  var XHR = new XMLHttpRequest();
  XHR.onreadystatechange = function() {
    if (XHR.readyState == 4) {/*
      gLog.appendChild(document.createElement("li"))
          .appendChild(document.createTextNode(aURL + " - " + XHR.status +
                                               " " + XHR.statusText));*/
    }
    if (XHR.readyState == 4 && XHR.status == 200) {
      // so far so good
      if (XHR.responseXML != null && aFormat == "xml" &&
          XHR.responseXML.getElementById('test').firstChild.data)
        aCallback(aXHR.responseXML.getElementById('test').firstChild.data);
      else if (XHR.responseText != null && aFormat == "json")
        aCallback(JSON.parse(XHR.responseText));
      else
        aCallback(XHR.responseText);
    } else if (XHR.readyState == 4 && XHR.status != 200) {
      // fetched the wrong page or network error...
      console.log("ERROR: XHR status " + XHR.status + " - " + aURL);
      aCallback(null);
    }
  };
  XHR.open("GET", aURL);
  if (gSocorroAPIToken) {
    // XXX: Should work but doesn't yet! We'll need to figure this out.
    //      Use this path when we have a token so bug 1143424 can be tested.
    XHR.setRequestHeader("Auth-Token", gSocorroAPIToken);
  }
  if (aFormat == "json") { XHR.setRequestHeader("Accept", "application/json"); }
  else if (aFormat == "xml") { XHR.setRequestHeader("Accept", "application/xml"); }
  try {
    XHR.send();
  }
  catch (e) {
    console.log("ERROR: XHR send - " + e + " - " + aURL);
    aCallback(null);
  }
}

function makeDate(aDate) {
  // Date format is YYYY-MM-DD
  // Note that .getMonth() returns a number between 0 and 11 (0 for January)!
  return aDate.getUTCFullYear() + "-" +
         (aDate.getUTCMonth() < 9 ? "0" : "") + (aDate.getUTCMonth() + 1 ) + "-" +
         (aDate.getUTCDate() < 10 ? "0" : "") + aDate.getUTCDate();
}

function getParameterByName(aName) {
  // from http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
  name = aName.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
  var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
      results = regex.exec(location.search);
  return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
<|endoftext|>


<commit_before>Handler = {
    /**
     * Display a user's message
     */
    event_message: function handle_event_message(e){
        var rooms = JSON.parse($('#rooms').val());
        var data = JSON.parse(e.data);

        /**
         * don't display the message if the user is not on the room
         * where the message comes from
         */
        if(-1 === rooms.indexOf(data['room'])){
            return;
        }

        var lineDiv = document.createElement('div');

        var timeSpan = document.createElement('span');
        var nickSpan = document.createElement('span');
        var msgSpan = document.createElement('span');

        timeSpan.innerHTML = get_current_time() + ' ';
        timeSpan.className = 'time';

        nickSpan.innerHTML = data['nick'] + ': ';
        nickSpan.className = 'nick';

        msgSpan.innerHTML = data['message'] + ' ';
        msgSpan.className = 'msg';

        if(is_mention(data['message'])){
            lineDiv.className = 'line alert alert-info';
        }
        else{
            lineDiv.className = 'line';
        }

        lineDiv.appendChild(timeSpan);
        lineDiv.appendChild(nickSpan);
        lineDiv.appendChild(msgSpan);

        $('#' + data['room']).append(lineDiv);

        notify_activity(data['room']);

        $('div#content').trigger('update_scrollbar');
    },

    /**
     * Update the user list when someone joins the chat
     */
    event_users: function handle_event_users(e){
        var users = $.parseJSON(e.data);
        var usersDiv = $('#user-list')[0];

        usersDiv.innerHTML = '';

        for(i=0; i<users.length-1; i++){
            usersDiv.innerHTML += users[i] + ', ';
        }

        usersDiv.innerHTML += users[users.length-1];
    },

    /**
     * Callback to respond to ping events via AJAX
     */
    event_ping: function handle_event_ping(e){
        $.post($SCRIPT_ROOT + '/_pong', 'PONG!');
    },

    /**
     * Callback that handles message sending errors
     */
    publish_error: function handle_publish_error(e){
        var lineDiv = document.createElement('div');

        lineDiv.className = 'line alert alert-error';
        lineDiv.innerHTML = e.responseText;

        $('.tab-pane.active').append(lineDiv);
        $('div#content').trigger('update_scrollbar');
    },

    /**
     * Callback for handling SSE errors
     */
    event_error: function handle_event_error(e){
        this.close(); //here, `this` refers to `stream`
        console.log('err:', e);
        //TODO: display some error to the user here (some kind of pop up) with
        //e.data as the err msg, also show a reconnect btn with a timer for
        //auto-reconnect
        $('div#content').trigger('update_scrollbar');
    },

    /**
     * Callback that handles the join errors
     */
    join_error: function handle_join_error(e){
        var lineDiv = document.createElement('div');

        lineDiv.className = 'line alert alert-error';
        lineDiv.innerHTML = e.responseText;

        $('.tab-pane.active').append(lineDiv);
        $('div#content').trigger('update_scrollbar');
    },

    /**
     * Callback for handling the leave error (these occur when leaving rooms)
     */
    leave_room_error: function handle_leave_room_error(e){
        //if the status is 404 then the user closed his last room, so we logged
        //him out
        if(404 === e.status){
            window.location.replace($SCRIPT_ROOT);
        }
        else{
            var lineDiv = document.createElement('div');

            lineDiv.className = 'line alert alert-error';
            lineDiv.innerHTML = e.responseText;

            $('.tab-pane.active').append(lineDiv);
            $('div#content').trigger('update_scrollbar');
        }
    },
    /**
     * Move the scrollbar at the bottom in order to see the latest message
     */
    update_scrollbar: function handle_updatescrollbar(e){
        $('#content').scrollTop($('#content')[0].scrollHeight + 42);
    }
}

/**
 * Create an AJAX request that will publish the message the user wants to
 * transmit
 */
function publish_message(e){
    e.preventDefault();
    $.post($SCRIPT_ROOT + '/_publish_message', {'message': $('#text').val(),
        'room': $('.tab-pane.active').attr('id')}).fail(Handler.publish_error);
    $('#text').val('');
    $('#text').focus();
}

/**
 * Leave a room
 *
 * An AJAX request is made to the server which will respond with the new room
 * list, also the tabs will be updated
 *
 * @param string room the clicked room (the one the user wants to leave)
 * @param string active_room the room currently active
 */
function leave_room(room, active_room){
    var room_name = room.children().attr("href").slice(1);

    $.post($SCRIPT_ROOT + '/_leave_room', {'room': room_name})
        .fail(Handler.leave_room_error)
        .success(function(e){
            var active_room_name = active_room.children().attr("href").slice(1);

            if(active_room_name == room_name){

                var next_room = active_room.next();
                if(next_room.length){ //the current tab is not the last, go right
                    active_room.next().attr('class', 'active');
                }
                else{ //current tab is the last, move to left
                    active_room.prev().attr('class', 'active');
                }
            }
            else{
                active_room.attr('class', 'active');
            }

            $(room).remove();
            $('#' + room_name).remove();

            $('#rooms').val(e);
        });
}

/**
 * Display the rooms as tabs
 *
 * This function is able to decide whether it needs to create all the tabs or
 * just some of the tabs by array difference
 */
function display_rooms(){
    var rooms = JSON.parse($('#rooms').val());

    var close_btn = $('<button>').attr({
        class: 'close',
        onclick: 'leave_room($(this).parent().parent(), $("li.active"))'
    }).html('×');

    /**
     * starting from one when joining rooms at login,
     * because the first room is displayed outside the for loop
     * since I need to set some classes to "active"
     */
    var pos = 1;

    if($('.nav.nav-tabs').length == 0){ //the user joins the rooms at login
        $('#chat').append(
            $('
').attr({class: 'nav nav-tabs'}).append(
                $('
').attr({class: 'active'}).append(
                    $('').attr({
                        href: '#' + rooms[0],
                        'data-toggle': 'tab',
                    }).html(rooms[0] + close_btn.prop('outerHTML'))
                )
            )
        );

        $('#chat').append(
            $('
').attr({class: 'tab-content', id: 'content'}).append(
                $('
').attr({
                    class: 'tab-pane active',
                    id: rooms[0],
                })
            )
        );
    }
    else{ //the user joins the rooms after he logged in, so we display only the
          //new rooms since the rest are already displayed
        var current_rooms = get_current_rooms();

        rooms = rooms.filter(function(i){
            return current_rooms.indexOf(i) < 0;
        });

        /**
         * every room should be processed because if we join we don't
         * have to change the active room
         */
        pos = 0;
    }

    for(i=pos; i<rooms.length; i++){
        $('.nav.nav-tabs').append(
            $('
').append(
                $('').attr({
                    href: '#' + rooms[i],
                    'data-toggle': 'tab',
                }).html(rooms[i] + close_btn.prop('outerHTML'))
            )
        );

        $('.tab-content').append(
            $('
').attr({
                class: 'tab-pane',
                id: rooms[i],
            })
        );

        if(0 == pos && i == rooms.length - 1){
            options = {
                to: $('a[href="' + '#' + rooms[i] + '"]'),
                className: 'ui-effects-transfer'
            };
            $('[name="join"]').effect("transfer", options, 700);
        }
    }
}

/**
 * Get the currently joined rooms
 *
 * @return array of room names
 */
function get_current_rooms(){
    var current_rooms = [];
    $('.tab-pane').each(function(){
        current_rooms.push($(this).attr('id'))
    });

    return current_rooms;
}

/**
 * Create an AJAX request to join more rooms after logging in
 */
function join_rooms(e){
    e.preventDefault();
    $.post($SCRIPT_ROOT + '/_join_rooms', {'join_rooms': $('#join_rooms').val()})
        .fail(Handler.join_error)
        .success(function(e){
            $('#rooms').val(e);
            display_rooms();
            $('#join_rooms').val('');
        });
}

/**
 * Initialize the elements that compose the chat, the EventSource (for the Server Sent
 * Events) and display the rooms the user joined
 */
function load_chat(){
    var stream = new EventSource($SCRIPT_ROOT + '/_sse_stream');

    stream.onmessage = Handler.event_message;
    stream.onerror = Handler.event_error;

    stream.addEventListener('users', Handler.event_users);
    stream.addEventListener('ping', Handler.event_ping);

    display_rooms();
    $('#text').focus();
}

/**
 * Get the current time
 *
 * @return string the current time in HH:MM format
 */
function get_current_time(){
    var currentTime = new Date();

    var h = currentTime.getHours();
    var m = currentTime.getMinutes();

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

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

    return h + ':' + m;
}

/**
 * Check if the user is mentioned in the message
 *
 * A user is mentioned when his nick appears as a word in a message:
 * "hello @nick"
 * "nick, hello"
 * "nick: hi"
 * "how are you nick?"
 *
 * The nick may be surrounded by punctuation, symbols or spaces, but not other
 * letters, so for the following message this function will return false:
 * "hello foonick"
 *
 * @param message the message that is checked for mentions
 *
 * @return bool true if the user is mentioned, else false
 */
function is_mention(message){
    var nick = $('label[for="text"]').html().slice(0, -1);
    var pos = message.indexOf(nick);

    while(-1 !== pos){
        if((pos === 0 || (pos - 1 >= 0 && !is_letter(message[pos-1]))) &&
           (pos+nick.length == message.length ||
                (pos+nick.length < message.length &&
                !is_letter(message[pos+nick.length])
                )
           )){
            return true;
        }

        message = message.slice(pos+nick.length);
        pos = message.indexOf(nick);
    }

    return false;
}

/**
 * Check if a character is a letter
 *
 * @param l string this should be a single character to be checked
 *
 * @return bool true if l is a letter, else false
 */
function is_letter(l){
    if((l >= 'a' && l <= 'z') || (l >= 'A' && l <= 'Z')){
        return true;
    }

    return false;
}

/**
 * Notify the user that there is activity on the chat
 *
 * @param string room_name room's name where is activity
 */
function notify_activity(room_name){
    //create an activity notice if the tab is not focused
    var a = $('a[href="#' + room_name + '"]');
    if('active' !== a.parent().attr('class')){
        if(0 === $('#icon-' + room_name).length){ //prepend just one icon
            a.prepend($('').attr({
                class: 'icon-comment',
                id: 'icon-' + room_name,
            }));
        }
    }

    if(away){
        Notificon("!", {
            font: '10px arial',
            color: '#ffffff',
            stroke: 'rgba(240, 61, 37, 1)',
            width: 7,
        });
    }
}

/**
 * Add horizontal rules to each object in an array
 *
 * If there are existing
 elements they are removed and a new one is added
 *
 * @param array obj array of objects to add a horizontal rule to
 */
function add_hr(obj){
    for(i=0; i<obj.length; i++){
        if($(obj[i]).has('div.line').length){
            obj[i].innerHTML = obj[i].innerHTML.split('
').join('') + '
';
        }
    }

    $('div#content').trigger('update_scrollbar');
}

/**
 * Adjust the height of the content where the messages appear in order to keep
 * the whole app on the screen.
 */
function adjust_blocks() {
    var win_h = $(window).height();
    var footer_h = $('p.footer').outerHeight(true);
    var box_h = $('div.well').outerHeight(true);
    var tabs_h = $('.nav.nav-tabs').outerHeight(true);
    var body_margins_h = parseInt($('body').css('margin-top')) +
        parseInt($('body').css('margin-bottom'));

    $('#content').css('height', win_h-box_h-footer_h-tabs_h-body_margins_h);
}

/**
 * If the user presses Enter while he's on the message textfield (#text) the
 * message is sent, if he presses Ctrl + Enter a newline is added to the message
 *
 * If the user presses Enter while on the rooms textfield (#join_rooms) he will
 * join them
 */
$(document).keypress(function(e){
    if('join_rooms' == e.target.id && 13 == e.which){
        join_rooms(e);
    }
    else if('text' == e.target.id && (10 == e.which
            //this part is needed in order to solve browser incompatibilities
            || 13 == e.which && e.ctrlKey)){
        $('#text').val($('#text').val() + '\n');
        return false;
    }
    else if('text' == e.target.id && 13 == e.which){
        publish_message(e);
    }
});

//Global initializations
load_chat();

//clear the activity notice upon clicking on the tab
$('a[data-toggle="tab"]').on('show', function(e){
    $('#icon-' + $(e.target).attr('href').slice(1)).remove();

    //add a
 to the last tab in order to mark the activity on that room
    //since the user moved away
    if(typeof e.relatedTarget !== 'undefined'){
        add_hr($('div' + $(e.relatedTarget).attr('href')));
    }
});

//after the tab is shown scroll down
$('a[data-toggle="tab"]').on('shown', function(e){
    Handler.update_scrollbar();
});

//if the browser or the browser's tab is not focused display a Notificon
$(window).focus(function(){
    away = false;
    Notificon('');
});

$(window).blur(function(){
    away = true;

    add_hr($('div.tab-pane'));
});

var away = false;

$('[name="send"]').click(publish_message);
$('[name="join"]').click(join_rooms);

$('div#content').bind('update_scrollbar', Handler.update_scrollbar);

//TODO move other handlers into Handler

$(document).ready(adjust_blocks);
$(window).resize(adjust_blocks);
<commit_msg>avoid empty lines when the server is down<commit_after>Handler = {
    /**
     * Display a user's message
     */
    event_message: function handle_event_message(e){
        var rooms = JSON.parse($('#rooms').val());
        var data = JSON.parse(e.data);

        /**
         * don't display the message if the user is not on the room
         * where the message comes from
         */
        if(-1 === rooms.indexOf(data['room'])){
            return;
        }

        var lineDiv = document.createElement('div');

        var timeSpan = document.createElement('span');
        var nickSpan = document.createElement('span');
        var msgSpan = document.createElement('span');

        timeSpan.innerHTML = get_current_time() + ' ';
        timeSpan.className = 'time';

        nickSpan.innerHTML = data['nick'] + ': ';
        nickSpan.className = 'nick';

        msgSpan.innerHTML = data['message'] + ' ';
        msgSpan.className = 'msg';

        if(is_mention(data['message'])){
            lineDiv.className = 'line alert alert-info';
        }
        else{
            lineDiv.className = 'line';
        }

        lineDiv.appendChild(timeSpan);
        lineDiv.appendChild(nickSpan);
        lineDiv.appendChild(msgSpan);

        $('#' + data['room']).append(lineDiv);

        notify_activity(data['room']);

        $('div#content').trigger('update_scrollbar');
    },

    /**
     * Update the user list when someone joins the chat
     */
    event_users: function handle_event_users(e){
        var users = $.parseJSON(e.data);
        var usersDiv = $('#user-list')[0];

        usersDiv.innerHTML = '';

        for(i=0; i<users.length-1; i++){
            usersDiv.innerHTML += users[i] + ', ';
        }

        usersDiv.innerHTML += users[users.length-1];
    },

    /**
     * Callback to respond to ping events via AJAX
     */
    event_ping: function handle_event_ping(e){
        $.post($SCRIPT_ROOT + '/_pong', 'PONG!');
    },

    /**
     * Callback that handles message sending errors
     */
    publish_error: function handle_publish_error(e){
        var lineDiv = document.createElement('div');

        lineDiv.className = 'line alert alert-error';

        if("" != e.responseText){
            lineDiv.innerHTML = e.responseText;
        }
        else{
            lineDiv.innerHTML = "Your message cannot be published, please reconnect!";
        }

        $('.tab-pane.active').append(lineDiv);
        $('div#content').trigger('update_scrollbar');
    },

    /**
     * Callback for handling SSE errors
     */
    event_error: function handle_event_error(e){
        this.close(); //here, `this` refers to `stream`
        console.log('err:', e);
        //TODO: display some error to the user here (some kind of pop up) with
        //e.data as the err msg, also show a reconnect btn with a timer for
        //auto-reconnect
        $('div#content').trigger('update_scrollbar');
    },

    /**
     * Callback that handles the join errors
     */
    join_error: function handle_join_error(e){
        var lineDiv = document.createElement('div');

        lineDiv.className = 'line alert alert-error';
        lineDiv.innerHTML = e.responseText;

        $('.tab-pane.active').append(lineDiv);
        $('div#content').trigger('update_scrollbar');
    },

    /**
     * Callback for handling the leave error (these occur when leaving rooms)
     */
    leave_room_error: function handle_leave_room_error(e){
        //if the status is 404 then the user closed his last room, so we logged
        //him out
        if(404 === e.status){
            window.location.replace($SCRIPT_ROOT);
        }
        else{
            var lineDiv = document.createElement('div');

            lineDiv.className = 'line alert alert-error';
            lineDiv.innerHTML = e.responseText;

            $('.tab-pane.active').append(lineDiv);
            $('div#content').trigger('update_scrollbar');
        }
    },
    /**
     * Move the scrollbar at the bottom in order to see the latest message
     */
    update_scrollbar: function handle_updatescrollbar(e){
        $('#content').scrollTop($('#content')[0].scrollHeight + 42);
    }
}

/**
 * Create an AJAX request that will publish the message the user wants to
 * transmit
 */
function publish_message(e){
    e.preventDefault();
    $.post($SCRIPT_ROOT + '/_publish_message', {'message': $('#text').val(),
        'room': $('.tab-pane.active').attr('id')}).fail(Handler.publish_error);
    $('#text').val('');
    $('#text').focus();
}

/**
 * Leave a room
 *
 * An AJAX request is made to the server which will respond with the new room
 * list, also the tabs will be updated
 *
 * @param string room the clicked room (the one the user wants to leave)
 * @param string active_room the room currently active
 */
function leave_room(room, active_room){
    var room_name = room.children().attr("href").slice(1);

    $.post($SCRIPT_ROOT + '/_leave_room', {'room': room_name})
        .fail(Handler.leave_room_error)
        .success(function(e){
            var active_room_name = active_room.children().attr("href").slice(1);

            if(active_room_name == room_name){

                var next_room = active_room.next();
                if(next_room.length){ //the current tab is not the last, go right
                    active_room.next().attr('class', 'active');
                }
                else{ //current tab is the last, move to left
                    active_room.prev().attr('class', 'active');
                }
            }
            else{
                active_room.attr('class', 'active');
            }

            $(room).remove();
            $('#' + room_name).remove();

            $('#rooms').val(e);
        });
}

/**
 * Display the rooms as tabs
 *
 * This function is able to decide whether it needs to create all the tabs or
 * just some of the tabs by array difference
 */
function display_rooms(){
    var rooms = JSON.parse($('#rooms').val());

    var close_btn = $('<button>').attr({
        class: 'close',
        onclick: 'leave_room($(this).parent().parent(), $("li.active"))'
    }).html('×');

    /**
     * starting from one when joining rooms at login,
     * because the first room is displayed outside the for loop
     * since I need to set some classes to "active"
     */
    var pos = 1;

    if($('.nav.nav-tabs').length == 0){ //the user joins the rooms at login
        $('#chat').append(
            $('
').attr({class: 'nav nav-tabs'}).append(
                $('
').attr({class: 'active'}).append(
                    $('').attr({
                        href: '#' + rooms[0],
                        'data-toggle': 'tab',
                    }).html(rooms[0] + close_btn.prop('outerHTML'))
                )
            )
        );

        $('#chat').append(
            $('
').attr({class: 'tab-content', id: 'content'}).append(
                $('
').attr({
                    class: 'tab-pane active',
                    id: rooms[0],
                })
            )
        );
    }
    else{ //the user joins the rooms after he logged in, so we display only the
          //new rooms since the rest are already displayed
        var current_rooms = get_current_rooms();

        rooms = rooms.filter(function(i){
            return current_rooms.indexOf(i) < 0;
        });

        /**
         * every room should be processed because if we join we don't
         * have to change the active room
         */
        pos = 0;
    }

    for(i=pos; i<rooms.length; i++){
        $('.nav.nav-tabs').append(
            $('
').append(
                $('').attr({
                    href: '#' + rooms[i],
                    'data-toggle': 'tab',
                }).html(rooms[i] + close_btn.prop('outerHTML'))
            )
        );

        $('.tab-content').append(
            $('
').attr({
                class: 'tab-pane',
                id: rooms[i],
            })
        );

        if(0 == pos && i == rooms.length - 1){
            options = {
                to: $('a[href="' + '#' + rooms[i] + '"]'),
                className: 'ui-effects-transfer'
            };
            $('[name="join"]').effect("transfer", options, 700);
        }
    }
}

/**
 * Get the currently joined rooms
 *
 * @return array of room names
 */
function get_current_rooms(){
    var current_rooms = [];
    $('.tab-pane').each(function(){
        current_rooms.push($(this).attr('id'))
    });

    return current_rooms;
}

/**
 * Create an AJAX request to join more rooms after logging in
 */
function join_rooms(e){
    e.preventDefault();
    $.post($SCRIPT_ROOT + '/_join_rooms', {'join_rooms': $('#join_rooms').val()})
        .fail(Handler.join_error)
        .success(function(e){
            $('#rooms').val(e);
            display_rooms();
            $('#join_rooms').val('');
        });
}

/**
 * Initialize the elements that compose the chat, the EventSource (for the Server Sent
 * Events) and display the rooms the user joined
 */
function load_chat(){
    var stream = new EventSource($SCRIPT_ROOT + '/_sse_stream');

    stream.onmessage = Handler.event_message;
    stream.onerror = Handler.event_error;

    stream.addEventListener('users', Handler.event_users);
    stream.addEventListener('ping', Handler.event_ping);

    display_rooms();
    $('#text').focus();
}

/**
 * Get the current time
 *
 * @return string the current time in HH:MM format
 */
function get_current_time(){
    var currentTime = new Date();

    var h = currentTime.getHours();
    var m = currentTime.getMinutes();

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

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

    return h + ':' + m;
}

/**
 * Check if the user is mentioned in the message
 *
 * A user is mentioned when his nick appears as a word in a message:
 * "hello @nick"
 * "nick, hello"
 * "nick: hi"
 * "how are you nick?"
 *
 * The nick may be surrounded by punctuation, symbols or spaces, but not other
 * letters, so for the following message this function will return false:
 * "hello foonick"
 *
 * @param message the message that is checked for mentions
 *
 * @return bool true if the user is mentioned, else false
 */
function is_mention(message){
    var nick = $('label[for="text"]').html().slice(0, -1);
    var pos = message.indexOf(nick);

    while(-1 !== pos){
        if((pos === 0 || (pos - 1 >= 0 && !is_letter(message[pos-1]))) &&
           (pos+nick.length == message.length ||
                (pos+nick.length < message.length &&
                !is_letter(message[pos+nick.length])
                )
           )){
            return true;
        }

        message = message.slice(pos+nick.length);
        pos = message.indexOf(nick);
    }

    return false;
}

/**
 * Check if a character is a letter
 *
 * @param l string this should be a single character to be checked
 *
 * @return bool true if l is a letter, else false
 */
function is_letter(l){
    if((l >= 'a' && l <= 'z') || (l >= 'A' && l <= 'Z')){
        return true;
    }

    return false;
}

/**
 * Notify the user that there is activity on the chat
 *
 * @param string room_name room's name where is activity
 */
function notify_activity(room_name){
    //create an activity notice if the tab is not focused
    var a = $('a[href="#' + room_name + '"]');
    if('active' !== a.parent().attr('class')){
        if(0 === $('#icon-' + room_name).length){ //prepend just one icon
            a.prepend($('').attr({
                class: 'icon-comment',
                id: 'icon-' + room_name,
            }));
        }
    }

    if(away){
        Notificon("!", {
            font: '10px arial',
            color: '#ffffff',
            stroke: 'rgba(240, 61, 37, 1)',
            width: 7,
        });
    }
}

/**
 * Add horizontal rules to each object in an array
 *
 * If there are existing
 elements they are removed and a new one is added
 *
 * @param array obj array of objects to add a horizontal rule to
 */
function add_hr(obj){
    for(i=0; i<obj.length; i++){
        if($(obj[i]).has('div.line').length){
            obj[i].innerHTML = obj[i].innerHTML.split('
').join('') + '
';
        }
    }

    $('div#content').trigger('update_scrollbar');
}

/**
 * Adjust the height of the content where the messages appear in order to keep
 * the whole app on the screen.
 */
function adjust_blocks() {
    var win_h = $(window).height();
    var footer_h = $('p.footer').outerHeight(true);
    var box_h = $('div.well').outerHeight(true);
    var tabs_h = $('.nav.nav-tabs').outerHeight(true);
    var body_margins_h = parseInt($('body').css('margin-top')) +
        parseInt($('body').css('margin-bottom'));

    $('#content').css('height', win_h-box_h-footer_h-tabs_h-body_margins_h);
}

/**
 * If the user presses Enter while he's on the message textfield (#text) the
 * message is sent, if he presses Ctrl + Enter a newline is added to the message
 *
 * If the user presses Enter while on the rooms textfield (#join_rooms) he will
 * join them
 */
$(document).keypress(function(e){
    if('join_rooms' == e.target.id && 13 == e.which){
        join_rooms(e);
    }
    else if('text' == e.target.id && (10 == e.which
            //this part is needed in order to solve browser incompatibilities
            || 13 == e.which && e.ctrlKey)){
        $('#text').val($('#text').val() + '\n');
        return false;
    }
    else if('text' == e.target.id && 13 == e.which){
        publish_message(e);
    }
});

//Global initializations
load_chat();

//clear the activity notice upon clicking on the tab
$('a[data-toggle="tab"]').on('show', function(e){
    $('#icon-' + $(e.target).attr('href').slice(1)).remove();

    //add a
 to the last tab in order to mark the activity on that room
    //since the user moved away
    if(typeof e.relatedTarget !== 'undefined'){
        add_hr($('div' + $(e.relatedTarget).attr('href')));
    }
});

//after the tab is shown scroll down
$('a[data-toggle="tab"]').on('shown', function(e){
    Handler.update_scrollbar();
});

//if the browser or the browser's tab is not focused display a Notificon
$(window).focus(function(){
    away = false;
    Notificon('');
});

$(window).blur(function(){
    away = true;

    add_hr($('div.tab-pane'));
});

var away = false;

$('[name="send"]').click(publish_message);
$('[name="join"]').click(join_rooms);

$('div#content').bind('update_scrollbar', Handler.update_scrollbar);

//TODO move other handlers into Handler

$(document).ready(adjust_blocks);
$(window).resize(adjust_blocks);
<|endoftext|>


<commit_before>/* global describe */
/* global beforeEach */
/* global it */
/* global expect */
/* global spyOn */
/* global inject */
'use strict';

describe('ProjectUpdateAccessService', function() {
  var ProjectUpdateAccessService;
  var Principal;
  var CurrentProjectService;

  beforeEach(module('metadatamanagementApp'));
  beforeEach(inject(function(_ProjectUpdateAccessService_, _Principal_,
                             _CurrentProjectService_) {
    ProjectUpdateAccessService = _ProjectUpdateAccessService_;
    Principal = _Principal_;
    CurrentProjectService = _CurrentProjectService_;
  }));

  it('should return false if no project is selected', function() {
    spyOn(CurrentProjectService, 'getCurrentProject')
      .and.returnValue(undefined);

    expect(ProjectUpdateAccessService.isUpdateAllowed()).toBe(false);
  });

  it('should return true if an updateable project is selected', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'PUBLISHER'
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed()).toBe(true);
  });

  it('should return true if an updateable project is selected', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        surveysState : {
          publisherReady : false,
          dataProviderReady : false
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'surveys')).toBe(true);
  });

  it('should return false if an project is marked as dataProviderReady', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        surveysState : {
          publisherReady : false,
          dataProviderReady : true
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'surveys')).toBe(false);
  });

  it('should return false if a project has been released', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      release: {}
    });

    expect(ProjectUpdateAccessService.isUpdateAllowed()).toBe(false);
  });

  it('should return false if the assigneeGroup doesn\'t match the current' +
    'user\'s role', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER'
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(false);

    expect(ProjectUpdateAccessService.isUpdateAllowed()).toBe(false);
  });

  it('should return false if assigneeGroup is unknown', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: ''
    });
    expect(ProjectUpdateAccessService.isUpdateAllowed()).toBe(false);
  });

  it('should not call CurrentProjectService if the project was given as a ' +
    'parameter', function() {
    var spy = spyOn(CurrentProjectService, 'getCurrentProject').and
      .callThrough();
    ProjectUpdateAccessService.isUpdateAllowed({});
    expect(spy).not.toHaveBeenCalled();
  });
});
<commit_msg>#1844 coverage for more branches in Jasmine test<commit_after>/* global describe */
/* global beforeEach */
/* global it */
/* global expect */
/* global spyOn */
/* global inject */
'use strict';

describe('ProjectUpdateAccessService', function() {
  var ProjectUpdateAccessService;
  var Principal;
  var CurrentProjectService;

  beforeEach(module('metadatamanagementApp'));
  beforeEach(inject(function(_ProjectUpdateAccessService_, _Principal_,
                             _CurrentProjectService_) {
    ProjectUpdateAccessService = _ProjectUpdateAccessService_;
    Principal = _Principal_;
    CurrentProjectService = _CurrentProjectService_;
  }));

  it('should return false if no project is selected', function() {
    spyOn(CurrentProjectService, 'getCurrentProject')
      .and.returnValue(undefined);

    expect(ProjectUpdateAccessService.isUpdateAllowed()).toBe(false);
  });

  it('should return true if an updateable project is selected', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'PUBLISHER'
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed()).toBe(true);
  });

  it('should return true if an updateable project is selected', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        surveysState : {
          publisherReady : false,
          dataProviderReady : false
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'surveys')).toBe(true);
  });

  it('should return false if an project is marked as dataProviderReady', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        surveysState : {
          publisherReady : false,
          dataProviderReady : true
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'surveys')).toBe(false);
  });
  it('should return true if an updateable project is selected', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        studiesState : {
          publisherReady : false,
          dataProviderReady : false
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'studies')).toBe(true);
  });

  it('should return false if an project is marked as dataProviderReady', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        studiesState : {
          publisherReady : false,
          dataProviderReady : true
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'studies')).toBe(false);
  });

  it('should return true if an updateable project is selected', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        instrumentsState : {
          publisherReady : false,
          dataProviderReady : false
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'instruments')).toBe(true);
  });

  it('should return false if an project is marked as dataProviderReady', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        instrumentsState : {
          publisherReady : false,
          dataProviderReady : true
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'instruments')).toBe(false);
  });

  it('should return true if an updateable project is selected', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        variablesState : {
          publisherReady : false,
          dataProviderReady : false
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'variables')).toBe(true);
  });

  it('should return false if an project is marked as dataProviderReady', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        variablesState : {
          publisherReady : true,
          dataProviderReady : true
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'variables')).toBe(false);
  });

  it('should return true if an updateable project is selected', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        dataSetsState : {
          publisherReady : false,
          dataProviderReady : false
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'data_sets')).toBe(true);
  });

  it('should return false if an project is marked as dataProviderReady', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        dataSetsState : {
          publisherReady : true,
          dataProviderReady : false
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'data_sets')).toBe(false);
  });

  it('should return true if an updateable project is selected', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        questionsState : {
          publisherReady : false,
          dataProviderReady : false
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'questions')).toBe(true);
  });

  it('should return false if an project is marked as dataProviderReady', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER',
      configuration : {
        questionsState : {
          publisherReady : true,
          dataProviderReady : false
        }
      }
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(true);

    expect(ProjectUpdateAccessService.isUpdateAllowed(undefined, 'questions')).toBe(false);
  });

  it('should return false if a project has been released', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      release: {}
    });

    expect(ProjectUpdateAccessService.isUpdateAllowed()).toBe(false);
  });

  it('should return false if the assigneeGroup doesn\'t match the current' +
    'user\'s role', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: 'DATA_PROVIDER'
    });
    spyOn(Principal, 'hasAuthority').and.returnValue(false);

    expect(ProjectUpdateAccessService.isUpdateAllowed()).toBe(false);
  });

  it('should return false if assigneeGroup is unknown', function() {
    spyOn(CurrentProjectService, 'getCurrentProject').and.returnValue({
      assigneeGroup: ''
    });
    expect(ProjectUpdateAccessService.isUpdateAllowed()).toBe(false);
  });

  it('should not call CurrentProjectService if the project was given as a ' +
    'parameter', function() {
    var spy = spyOn(CurrentProjectService, 'getCurrentProject').and
      .callThrough();
    ProjectUpdateAccessService.isUpdateAllowed({});
    expect(spy).not.toHaveBeenCalled();
  });
});
<|endoftext|>