import socket import threading import time class Watchdog(threading.Thread): '''Execute a function when there has been a period of length timeout after last call to .update() ''' def __init__(self, timeout, function, interval = 1.0): threading.Thread.__init__(self) self.timeout = timeout self.interval = interval self.function = function self.stopevent = threading.Event() def start(self): self.update() threading.Thread.start(self) def update(self): self.timestamp = time.time() + self.timeout def run(self): while True: self.stopevent.wait(self.interval) if self.stopevent.isSet(): break if self.timestamp < time.time(): self.function() break def stop(self): self.stopevent.set() class Connection: def __init__(self, addr, server, rpctimeout = 30.0): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.settimeout(1.0) self.socket.connect(addr) self.server = server self.server.connections[hash(self)] = self self.watchdog = Watchdog(rpctimeout, self.rpc_close) self.watchdog.start() self.semaphore = threading.Semaphore() self._rbuf = "" self.ingame = False # Some functions can't be performed after we join a game def _readline(self): from time import time from socket import error timeout = self.socket.gettimeout() self.socket.setblocking(False) tstart = time() + timeout while "\n" not in self._rbuf and tstart >= time(): try: self._rbuf += self.socket.recv(1024) except error, (num, desc): if num != 11: raise else: pass self.socket.settimeout(timeout) if "\n" not in self._rbuf: # Don't return partial lines return "" pos = self._rbuf.find("\n") + 1 result = self._rbuf[:pos] self._rbuf = self._rbuf[pos:] return result def rpc_read(self): '''Read a line from the socket. Any line-terminating characters are present on the output. ''' self.semaphore.acquire() self.watchdog.update() result = self._readline() #result += data #if data == "" or data[-1] in "\r\n\0\x04": #break self.semaphore.release() return result def rpc_write(self, data): '''Write to the socket.''' self.semaphore.acquire() self.watchdog.update() self.socket.send(data) self.semaphore.release() def rpc_settimeout(self, timeout): '''Set socket timeout.''' self.watchdog.update() self.socket.settimeout(timeout) def rpc_keepalive(self): '''Just keep the connection alive.''' self.semaphore.acquire() # Maybe something is blocking this? RPC will timeout then self.watchdog.update() self.semaphore.release() def rpc_close(self): '''Close the socket.''' del self.server.connections[hash(self)] self.watchdog.stop() self.socket.close() # Pre-game commands # FIXME: Should these be in a separate class? def rpc_list(self): assert not self.ingame games = {} self.semaphore.acquire() self.socket.send("list\n") while True: data = self._readline() if data == "game list ends\n": break r = re.findall("game number ([+\-0-9]+) type ([+\-0-9]+) clients (\[[^]]*\]) info (\{[^}]*\})", data) if len(r) == 0: break gameid, gametype, clients, info = r[0] games[int(gameid)] = {'type': int(gametype), 'clients': eval_const(clients), 'info': eval_const(info)} self.semaphore.release() return games def rpc_join(self, gameid, name): assert not self.ingame self.rpc_write("join %d '%s'\n" % (gameid, name)) self.ingame = True def rpc_create(self, gametype, name, info): assert not self.ingame self.rpc_write("create %d '%s' %s" % (gametype, name, info)) self.ingame = True