def tickWrap(self): try: self.tick() return True except Exception, e: print "EventThread error:" if self.gameCon is not None: self.gameCon.err = str(e) if hasattr(self.gameCon, 'game'): WSSpadesHandler.on_game_change(self.gameCon.game) traceback.print_exc() return False
def tick(self): if self.gameCon is None: # Get list of logs (['YYYY-mm-dd_HHMMSS.log']) # Unfortunately the server doesn't return an etag for this page; instead we cache based on the number of logs displayed, on the assumption that logs will never be removed req = requests.get(logURL) if req.status_code != 200: raise RuntimeError("Server returned %d looking up log list" % req.status_code) logs = map(str, lxml.html.fromstring(req.text).xpath('//a[substring-after(@href, ".")="log"]/@href')) logs = filter(None, map(Shim.onLogLoad, logs)) numGames = len(getGames()) if len(logs) < numGames: raise RuntimeError("Only got %d %s from server (have %d in database)" % (len(logs), 'log' if len(logs) == 1 else 'logs', numGames)) elif len(logs) == numGames: # No new logs return # Find the first one we don't have a game for for log in logs: if log not in db['games']: console('event thread', "Starting new game for %s" % log) self.gameCon = Shim.onGameCon(GameConstructor(log, self.onGameEnd)) break else: console('event thread', "No games in progress") return # Look for new events in the current log data = self.cachedGet(logURL + self.gameCon.logFilename) if data is None: # Nothing new console('event thread', "No new data in %s" % self.gameCon.logFilename) return elif data is False: # Server is down. Hopefully temporarily; try again next tick console('event thread', "Failed to fetch data from %s" % self.gameCon.logFilename) return elif len(data) < self.gameCon.logOffset: raise RuntimeError("Fetched %d-byte log file %s, but next event expected at %d" % (len(data), self.gameCon.logFilename, self.gameCon.logOffset)) while self.gameCon and self.gameCon.logOffset < len(data) and self.test != 0: line = data[self.gameCon.logOffset:data.index('\n', self.gameCon.logOffset)+1] print "%8d %s" % (self.gameCon.logOffset, line) originalLen = len(line) line = Shim.onLine(self.gameCon, self.gameCon.logOffset, unpretty(line)) if line is None: self.gameCon.logOffset += originalLen continue # print "Searching for pattern at %s offset %d: %s" % (self.gameCon.logFilename, self.gameCon.logOffset, line) for pattern, fns in eventPatterns: match = pattern.match(line) if match: for fn in fns: g = match.groupdict() # Copy; the original match groupdict is not changed below # Bit of a hack. We want to rewrite any group that contains a PLAY, but there's no way to tell now. Currently all those groups are named 'play', so we only rewrite those if 'play' in g: g['play'] = unpretty(g['play']) # Same hack with USER for k in ('user', 'user1', 'user2'): if k in g: shimmed = Shim.onUsername(g[k]) if shimmed != g[k]: self.gameCon.usernameShims[shimmed] = g[k] g[k] = shimmed tz = int(round((datetime.now() - datetime.utcnow()).total_seconds() / 3600)) event = {'ts': datetime.strptime(g['ts'], '%Y-%m-%d %H:%M:%S') + timedelta(hours = tz), 'off': self.gameCon.logOffset} del g['ts'] event.update(fn(**g)) if self.test > 0: self.test -= 1 event = Shim.onEvent(self.gameCon, self.gameCon.logOffset, event) if event is not None: self.gameCon.pump(event) # pump() may have triggered onGameEnd and killed the current gameCon if self.gameCon is not None: self.gameCon.logOffset += originalLen break else: raise RuntimeError("Unrecognized log line at %s:%d: %s" % (self.gameCon.logFilename, self.gameCon.logOffset, line)) if hasattr(self.gameCon, 'game'): self.gameCon.game.out() WSSpadesHandler.on_game_change(self.gameCon.game)