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()