from socket import * import os, sys, time, random from select import select from io import StringIO from weakref import WeakValueDictionary from .metastruct import * from common import httpserver, stdlog if __name__ == '__main__': os.chdir(os.path.dirname(sys.argv[0]) or os.curdir) META_SERVER_HTTP_PORT = 8050 META_SERVER_PORT = 8055 META_SERVER_UDP_PORT = 8055 IMAGE_DIR = "../bubbob/doc/images" ICONS = [open(os.path.join(IMAGE_DIR, s), 'rb').read() for s in os.listdir(IMAGE_DIR) if s.endswith('.png')] assert ICONS, "you need to run ../bubbob/doc/bonus-doc.py" MAX_SERVERS = 50 MAX_CONNEXIONS = 60 serversockets = {} class MetaServer: def __init__(self, port=META_SERVER_PORT, udpport=META_SERVER_UDP_PORT): s = socket(AF_INET, SOCK_STREAM) s.bind(('', port)) s.listen(5) self.parentsock = s self.ServersDict = {} self.ServersList = [] serversockets[s] = self.clientconnect, sys.exit self.udpsock = socket(AF_INET, SOCK_DGRAM) self.udpsock.bind(('', udpport)) serversockets[self.udpsock] = self.udp_message, None self.udpdata = [] def detach(self): pid = os.fork() if pid: print(pid) os._exit(0) # in the child process os.setsid() logfile = stdlog.LogFile(limitsize=131072) if logfile: print(file=logfile) print("Logging to", logfile.filename) fd = logfile.f.fileno() try: # detach from parent os.dup2(fd, 1) os.dup2(fd, 2) os.dup2(fd, 0) except OSError: pass logfile.close() # record pid f = open('pid', 'w') print(os.getpid(), file=f) f.close() def clientconnect(self): s, addr = self.parentsock.accept() Connexion(s, addr) def publish(self, server): key = server.serverkey if key in self.ServersDict: current = self.ServersDict[key] if current is server: return self.ServersList.remove(current) elif len(self.ServersDict) >= MAX_SERVERS: raise OverflowError self.ServersList.append(server) self.ServersDict[key] = server print('+', key) def unpublish(self, server): key = server.serverkey if key in self.ServersDict: current = self.ServersDict[key] if current is server: del self.ServersDict[key] self.ServersList.remove(server) print('-', key) def makelist(self): items = {} for srv in self.ServersList: items[srv.serverkey] = encodedict(srv.serverinfo) return encodedict(items) def getserver(self, key): return self.ServersDict[key] def udp_message(self): data, addr = self.udpsock.recvfrom(32) self.udpdata.append((data, addr)) if len(self.udpdata) > 50: del self.udpdata[0] class Connexion(MessageSocket): def __init__(self, s, addr): MessageSocket.__init__(self, s) self.serverinfo = { 'time': int(time.time()), 'icon': random.choice(ICONS), 'iconformat': 'png', } self.addr = addr self.key = '%s:%d' % addr self.serverkey = None print('[', self.key) self.backlinks = WeakValueDictionary() if len(serversockets) >= MAX_CONNEXIONS: self.disconnect() raise OverflowError serversockets[s] = self.receive, self.disconnect def disconnect(self): metaserver.unpublish(self) try: del serversockets[self.s] except KeyError: pass print(']', self.key) def msg_serverinfo(self, info, *rest): print('|', self.key) if len(info) > 15000: raise OverflowError info = decodedict(info) self.serverinfo.update(info) def msg_startserver(self, port, *rest): serverkey = '%s:%d' % (self.addr[0], port) if self.serverkey and self.serverkey != serverkey: metaserver.unpublish(self) self.serverkey = serverkey metaserver.publish(self) def msg_stopserver(self, *rest): metaserver.unpublish(self) def msg_list(self, *rest): self.s.sendall(message(RMSG_LIST, metaserver.makelist())) def msg_route(self, targetkey, *rest): try: target = metaserver.getserver(targetkey) except KeyError: try: target = self.backlinks[targetkey] except KeyError: self.s.sendall(message(RMSG_NO_HOST, targetkey)) return target.route(self, *rest) def route(self, origin, msgcode, *rest): self.backlinks[origin.key] = origin self.s.sendall(message(msgcode, origin.key, *rest)) def msg_traceback(self, tb, *rest): f = stdlog.LogFile('tb-%s.log' % (self.addr[0],)) if f: print(tb, file=f) f.close() def msg_udp_addr(self, pattern, *rest): for data, addr in metaserver.udpdata: if data == pattern: try: host, port = addr port = int(port) except ValueError: continue self.s.sendall(message(RMSG_UDP_ADDR, host, port)) return else: self.s.sendall(message(RMSG_UDP_ADDR)) MESSAGES = { MMSG_INFO: msg_serverinfo, MMSG_START: msg_startserver, MMSG_STOP: msg_stopserver, MMSG_LIST: msg_list, MMSG_ROUTE: msg_route, MMSG_TRACEBACK: msg_traceback, MMSG_UDP_ADDR: msg_udp_addr, } # ____________________________________________________________ import html.entities text_to_html = {} for key, value in list(html.entities.entitydefs.items()): text_to_html[value] = '&' + key + ';' for i in range(32): text_to_html[chr(i)] = '?' def htmlquote(s, getter=text_to_html.get): lst = [getter(c, c) for c in s if ' ' <= c < '\x7F'] return ''.join(lst) def txtfilter(s, maxlen=200): s = str(s)[:maxlen] l = [c for c in s if c in "!$*,-.0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ" "^_`abcdefghijklmnopqrstuvwxyz{|}"] return ''.join(l) f = open('index.html', 'r') HEADER, ROW, FOOTER = f.read().split('\\') f.close() def makehosthtml(srv, bottommsg, join): info = srv.serverinfo hostname, port = srv.serverkey.split(':') try: fullhostname = gethostbyaddr(hostname)[0] except: fullhostname = hostname url = None if join: url = "http://%s/join.html?host=%s&port=%s&httpport=%s&m=%s" % ( join, hostname, port, info.get('httpport') or 'off', time.time()) else: try: httpport = int(info.get('httpport')) except: pass else: url = "http://%s:%s/" % (hostname, httpport) javamsg = """

Click on a server above to join the game. This only works if:

Alternatively, install the Python version of the client, which can cope with all of the above problems.


""" if javamsg not in bottommsg: bottommsg.append(javamsg) result = '%s:%s' % (fullhostname, port) if url: result = '%s' % (url, result) return result def indexloader(headers, join=[], head=[], **options): if join: join = join[0] data = [HEADER % (head and head[0] or '')] bottommsg = [] if metaserver.ServersList: counter = 0 for srv in metaserver.ServersList: info = srv.serverinfo icon = '' % srv.serverkey bgcolor = ('#C0D0D0', '#E0D0A8')[counter&1] hosthtml = makehosthtml(srv, bottommsg, join) desc = htmlquote(info.get('desc')) or '' extradesc = htmlquote(info.get('extradesc')) or '' if isinstance(info.get('time'), int): stime = time.strftime('%a %b %d
%H:%M GMT', time.gmtime(info['time'])) else: stime = '' data.append(ROW % locals()) counter += 1 else: data.append(''' Sorry, there is no registered server at the moment. ''') if join: extrafooter = '''

Back to local games

''' % ( join, time.time()) else: extrafooter = '' bottommsg = '\n'.join(bottommsg) tbfiles = [s for s in os.listdir('.') if s.startswith('tb-')] if tbfiles: tbfiles = len(tbfiles) else: tbfiles = '' data.append(FOOTER % locals()) return StringIO(''.join(data)), 'text/html' def icoloader(key, **options): srv = metaserver.getserver(key[0]) iconformat = txtfilter(srv.serverinfo['iconformat'], 32) return StringIO(srv.serverinfo['icon']), 'image/' + iconformat httpserver.register('', indexloader) httpserver.register('index.html', indexloader) httpserver.register('bub-n-bros.html', indexloader) httpserver.register('ico', icoloader) httpserver.register('mbub.png', httpserver.fileloader('mbub.png', 'image/png')) httpserver.register('home.png', httpserver.fileloader('home.png', 'image/png')) def openhttpsocket(port = META_SERVER_HTTP_PORT): from http.server import HTTPServer class ServerClass(HTTPServer): def get_request(self): sock, addr = self.socket.accept() sock.settimeout(5.0) return sock, addr HandlerClass = httpserver.MiniHandler server_address = ('', port) httpd = ServerClass(server_address, HandlerClass) s = httpd.socket serversockets[s] = httpd.handle_request, None # ____________________________________________________________ def mainloop(): while 1: iwtd = list(serversockets.keys()) iwtd, owtd, ewtd = select(iwtd, [], iwtd) #print iwtd, owtd, ewtd, serversockets for s in iwtd: if s in serversockets: input, close = serversockets[s] try: eval(input()) except: import traceback print("-"*60) traceback.print_exc() print("-"*60) for s in ewtd: if s in serversockets: input, close = serversockets[s] try: close() except: import traceback print("-"*60) traceback.print_exc() print("-"*60) if __name__ == '__main__': metaserver = MetaServer() if sys.argv[1:2] == ['-f']: metaserver.detach() try: openhttpsocket() print('listening to client port tcp %d / http %d / udp %d.' % ( META_SERVER_PORT, META_SERVER_HTTP_PORT, META_SERVER_UDP_PORT)) mainloop() finally: if metaserver.ServersList: print('*** servers still connected, waiting 5 seconds') time.sleep(5) print('*** leaving at', time.ctime())