def on_close(self): for channel in self.channels: channels[channel].remove(self) if len(channels[channel]) == 0: del channels[channel] handlers.remove(self) console('websocket', "Closed")
def onGameEnd(self, game): console('event thread', "Game over: %s" % self.gameCon.logFilename) game.out() db['games'][self.gameCon.logFilename] = game self.gameCon = None # After this tick, immediately check again for a new game. This is mostly used when populating the database for the first time self.tickWait = False
def __init__(self, host, port): if StrictRedis: self.conn = StrictRedis(host=host, port=port) self.conn.pubsub() else: console("events", "Unable to load redis python module; event publishing disabled") self.conn = None
def load(self, method, url, *, cacheRead = False, cacheWrite = None, data = None, **params): if cacheWrite is None: cacheWrite = (method == 'get') if self.cache is None: cacheRead = cacheWrite = False if cacheRead: rtn = self.cache[(url, params, data)] if rtn is not None: return rtn console('jira api', f"API request: {url} {params} {data}") req = requests.request( method, url, params = params, data = json.dumps(data) if data else None, auth = self.auth, headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', }, ) if req.status_code == 204: return None if req.status_code not in (200, 201): messageExtractors = [ lambda json: json['message'], lambda json: json['errorMessages'][0], lambda json: list(json['errors'].values())[0], ] message = None for fn in messageExtractors: try: message = fn(req.json()) except: pass console('jira api', f"{req}: {req.text}") raise APIError(req.status_code, message, url) rtn = req.json() if method == 'get' and 'startAt' in rtn: # Result is paginated if 'startAt' in params: # This is one page of a paginated request being called from paginate(), just return it return rtn else: # This is the beginning of a paginated request return self.paginate(url, params, rtn, cacheWrite) else: # Result is not paginated, just return the whole thing if cacheWrite: self.cache[(url, params, data)] = rtn return rtn
def on_message(self, message): console('websocket', "Message received: %s" % message) try: data = fromJS(message) except: return if 'subscribe' in data and isinstance(data['subscribe'], list): addChannels = (set(data['subscribe']) - self.channels) self.channels |= addChannels for channel in addChannels: if channel not in channels: channels[channel] = set() channels[channel].add(self) if 'unsubscribe' in data and isinstance(data['unsubscribe'], list): rmChannels = (self.channels & set(data['unsubscribe'])) self.channels -= rmChannels for channel in rmChannels: channels[channel].remove(self) if len(channels[channel]) == 0: del channels[channel]
def wrap(): while True: console('websocket client', "Connecting to %s" % wsURL) ws = yield websocket_connect(wsURL) console('websocket client', 'Connected') # Trigger once immediately to fetch the current game while True: self.tickWrap() if self.tickWait: break else: self.tickWait = True while True: # Wait for a message, then poll the logs (we could theoretically just use the message directly, but this is easier to fit into the existing setup that expects offsets and timestamps) msg = yield ws.read_message() if msg is None: break console('websocket client', "Message: %s" % msg) self.tickWrap() console('websocket client', 'Disconnected')
def open(self): handlers.append(self) console('websocket', "Opened")
def run(self): app = tornado.web.Application([('/', WSSpadesHandler), ('/ws', WSSpadesHandler)]) app.listen(self.port, '0.0.0.0') console('websocket', "Listening") tornado.ioloop.IOLoop.instance().start()
def brick(msg): global brickMessage brickMessage = msg or True console('brick', "Bricked: %s", brickMessage)
def log_message(self, fmt, *args): console('rorn', "%s - %s", self.address_string(), fmt % args)
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)
# When python is started in the background it ignores SIGINT instead of throwing a KeyboardInterrupt def signal_die(signum, frame): signals = dict((getattr(signal, name), name) for name in dir(signal) if name.startswith('SIG') and '_' not in name) raise SystemExit("Caught %s; exiting" % signals.get(signum, "signal %d" % signum)) signal.signal(signal.SIGINT, signal.default_int_handler) signal.signal(signal.SIGTERM, signal_die) server = HTTPServer(('', config.localBindPort), HTTPHandler) HTTPHandler.enableStaticHandler('static') HTTPHandler.enableVue('components', 'views') try: console('main', 'Listening for connections') server.serve_forever() except KeyboardInterrupt: sys.__stdout__.write("\n\n") console('main', 'Exiting at user request') except (Exception, SystemExit) as e: sys.__stdout__.write("\n\n") console('main', '%s', e) console('main', 'Closing server sockets') server.server_close() console('main', 'Done')
if settings.redis: host, port = settings.redis.split(':', 1) port = int(port) addEventHandler(EventPublisher.EventPublisher(host, port)) # When python is started in the background it ignores SIGINT instead of throwing a KeyboardInterrupt def signal_die(signum, frame): signals = dict((getattr(signal, name), name) for name in dir(signal) if name.startswith('SIG') and '_' not in name) raise SystemExit("Caught %s; exiting" % signals.get(signum, "signal %d" % signum)) signal.signal(signal.SIGINT, signal.default_int_handler) signal.signal(signal.SIGTERM, signal_die) # Daemonize if option('daemon'): logFile = datetime.now().strftime('log-%Y%m%d-%H%M%S.log') console('main', "Switching to daemon mode. Output logged to %s" % logFile) # Double-fork if os.fork() != 0: os._exit(0) os.setsid() if os.fork() != 0: os._exit(0) # Point the standard file descriptors at a log file log = os.open(logFile, os.O_CREAT | os.O_RDWR) os.dup2(log, 0) os.dup2(log, 1) os.dup2(log, 2) # Writing the pidfile
def log_message(self, fmt, *args): user = self.session['user'].username if self.session and self.session['user'] else self.address_string() console('rorn', "%s(%s) %s", user or 'logged out', self.address_string(), fmt % args)
def log_message(self, fmt, *args): console("rorn", "%s - %s", self.address_string(), fmt % args)