Newer
Older
Import / projects / Gameloft / bne_lib / tools / metaserver / metaServer.py
import threading
import time
import connection
import stacktracer

class MetaConnectionHandler:
  def __init__(self, metaServer):
    self.metaServer = metaServer

    self.connection = None
    self.name = None
    self.address = [None, None]
    self.mode = None
    self.connectedStatus = False
    self.platform = 'unknown'
    self.chagned = False
    self.gameName = None
    self.deviceType = None
    self.credentials = None

    self.updateData = None
    self.updateCondition = threading.Condition()
    self.updateThread = threading.Thread(target = self._updateThread, name='Meta CH update thread')
    self.updateThread.start()

  def _updateThread(self):
    while not self.isDead():
      with self.updateCondition:
        self.updateCondition.wait(3)
        updateDataCopy = self.updateData
        self.updateData = None

      if updateDataCopy:
        self.connection.send('metastate', updateDataCopy)

  def init(self, host, name, connection):
    self.connection = connection
    self.address[0] = host
    self.name = name

  def scheduleUpdate(self, data):
    with self.updateCondition:
      self.updateData = data
      self.updateCondition.notify()

  def updateHandler(self, request):
    self.mode = request['mode']
    self.connectedStatus = request['connected']
    self.address[1] = request['port']
    self.platform = request['platform']
    self.gameName = request['gameName'] if 'gameName' in request else 'unknown_game'
    self.deviceType = request['deviceType'] if 'deviceType' in request else 'unknown_device'
    self.credentials = request['credentials'] if 'credentials' in request else '<unknown>'
    self.metaServer.scheduleUpdateForAllHandlers()

  def __str__(self):
    statusString = {
      None : 'unknown',
      True : 'connected',
      False : 'awaiting',
    }
    return '%s %s %s %s running %s at %s:%s is %s' % (self.platform, self.mode, self.name, self.deviceType, self.gameName, self.address[0], self.address[1], statusString[self.connectedStatus])

  def tr(self):
    if self.mode == 'host': return None
    editor = ''
    if self.gameName != '':
      editor = '<a role="button" class="btn btn-primary" href="%s-Tools.php?credential=%s">Edit</a>' % (self.gameName, self.credentials)
    html = '<tr align="center"><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>' % (self.deviceType, self.name, self.gameName, self.address[0], editor)
    return html

  def isDead(self):
    return self.connection and self.connection.isDead()

class MetaServer:
  def __init__(self, host, port):
    self.address = (host, port)
    self.connectionHandlers = []

  def start(self):
    self.accepterThread = threading.Thread(target = self._runUpdater, name='Meta updater')
    self.updaterThread = threading.Thread(target = self._runAccepter, name='Meta accepter')
    self.accepterThread.start()
    self.updaterThread.start()

  def _createStateData(self):
    res = []
    for c in self.connectionHandlers:
      cres = {}
      cres['name']       = c.name
      cres['mode']       = c.mode
      cres['platform']   = c.platform
      cres['connected']  = c.connectedStatus
      cres['host']       = c.address[0]
      cres['port']       = c.address[1]
      cres['gameName']   = c.gameName
      cres['deviceType'] = c.deviceType
      res.append(cres)

    return res

  def _runUpdater(self):
    while True:
      time.sleep(1)

      toDelete = []
      for ch in self.connectionHandlers:
        if ch.isDead():
          toDelete.append(ch)

      for ch in toDelete:
        self.connectionHandlers.remove(ch)

      changed = len(toDelete) > 0

      # remove dead duplicates
      for i, ch in list(enumerate(self.connectionHandlers)):
        for other in self.connectionHandlers[i+1:]:
          if ch.address[0] == other.address[0] and ch.mode == other.mode and ch.name == other.name:
            self.connectionHandlers.remove(ch)
            changed = True
            print 'Zombie connection purged'
            break

      if changed:
        self.scheduleUpdateForAllHandlers()

  def printReport(self):
    print
    print 'Report:'
    if not self.connectionHandlers:
      print '\t<Empty>'
    for ch in self.connectionHandlers:
      print '\t', ch

  def generateHtml(self):
    with open('connections.html', 'w') as reportFile:
      _print = lambda str: reportFile.write( '%s\n' % str )

      _print( '<html>' )
      _print( ' <head>' )
      _print( '  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">' )
      _print( '  <title>Available connections</title>' )
      _print( '  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>' )
      _print( '  <?php include $_SERVER[\'DOCUMENT_ROOT\']."/style.php"; ?>' )
      _print( ' </head>' )
      _print( ' <body>' )
      _print( '  <?php include $_SERVER[\'DOCUMENT_ROOT\']."/navbar.php"; ?>' )
      _print( '  <br>' )
      _print( '  <div class="container">' )
      _print( '   <div class="jumbotron">' )
      _print( '    <h1>Devices</h1>' )
      _print( '    <p><small>Show currently running game clients (includes Solar with the project loaded).</small></p>' )
      _print( '   </div>' )
      _print( '   <div class="panel panel-primary">' )
      _print( '    <div class="panel-heading"><h3 class="panel-title">Available connections</h3></div>' )
      _print( '    <table class="table table-condensed" style="border-collapse:collapse;">' )
      _print( '     <thead>' )
      _print( '      <tr align="center">' )
      _print( '       <td width="20%"><b>Device type</b></td>' )
      _print( '       <td width="20%"><b>Device Name</b></td>' )
      _print( '       <td width="20%"><b>Game Name</b></td>' )
      _print( '       <td width="20%"><b>IP</b></td>' )
      _print( '       <td width="20%"><b>Edit</b></td>' )
      _print( '      </tr>' )
      _print( '     </thead>' )
      _print( '     <tbody>' )
      for ch in self.connectionHandlers:
        tr = ch.tr()
        if tr: _print(tr)
      _print( '     </tbody>' )
      _print( '    </table>' )
      _print( '   </div>' )
      _print( '  </div>' )
      _print( ' </body>' )
      _print( '</html>' )

  def scheduleUpdateForAllHandlers(self):
    self.printReport()
    self.generateHtml()
    state = self._createStateData()
    for ch in self.connectionHandlers:
      if ch.mode == 'host':
        ch.scheduleUpdate(state)
    print 'Update sent'

  def _runAccepter(self):
    l = connection.Listener(self.address)
    while True:
      mc = MetaConnectionHandler(self)
      handlers = {'update' : mc.updateHandler}

      address, name, c = l.getConnection(handlers)
      mc.init(address[0], name, c)

      self.connectionHandlers.append(mc)

if __name__ == '__main__':
  stacktracer.trace_start('threads_trace.log')
  ms = MetaServer('', 7784)
  ms.start()