import time import json import re import vt100 auth_re = re.compile(b'^hello ([^ ]+) ([^ ]+)$') extra_data_re = re.compile(b'\033\[H(\000([^\377]*)\377)\033\[H\033\[2J') class Handler(object): def __init__(self, rows, cols): self.created_at = time.time() self.idle_since = time.time() self.rows = rows self.cols = cols self.buf = b'' self.vt = vt100.vt100(rows, cols) def process(self, data): # XXX this will break if the data is split between reads, but i don't # know if there's anything we can do about this, because we'll have # already processed the beginning of it if that happens. we might need # some better framing which allows us to detect the start of an out of # band section even when the end hasn't been received yet extra_data = {} while True: m = extra_data_re.search(data) if m is None: break extra_data_json = m.group(2) extra_data = json.loads(extra_data_json.decode('utf-8')) data = data[:m.start(1)] + data[m.end(1):] if "geometry" in extra_data: self.rows = extra_data["geometry"][1] self.cols = extra_data["geometry"][0] self.vt.set_window_size(self.rows, self.cols) self.buf += data clear = self.buf.rfind(b"\033[2J") if clear != -1: self.buf = self.buf[clear + 4:] self.vt.process(data) self.idle_since = time.time() def get_term(self): term = '' for i in range(0, self.rows): for j in range(0, self.cols): term += self.vt.cell(i, j).contents() term += "\n" return term[:-1] def total_time(self): return self._human_readable_duration(time.time() - self.created_at) def idle_time(self): return self._human_readable_duration(time.time() - self.idle_since) def _human_readable_duration(self, duration): days = 0 hours = 0 minutes = 0 seconds = 0 if duration > 60*60*24: days = duration // (60*60*24) duration -= days * 60*60*24 if duration > 60*60: hours = duration // (60*60) duration -= hours * 60*60 if duration > 60: minutes = duration // 60 duration -= minutes * 60 seconds = duration ret = "%02ds" % seconds if minutes > 0 or hours > 0 or days > 0: ret = ("%02dm" % minutes) + ret if hours > 0 or days > 0: ret = ("%02dh" % hours) + ret if days > 0: ret = ("%dd" % days) + ret return ret class Connection(object): def __init__(self, client, connection_id, publisher): self.client = client self.connection_id = connection_id self.publisher = publisher def run(self): buf = b'' while len(buf) < 1024 and b"\n" not in buf: buf += self.client.recv(1024) pos = buf.find(b"\n") if pos == -1: print("no authentication found") return auth = buf[:pos] if auth[-1:] == b"\r": auth = auth[:-1] buf = buf[pos+1:] m = auth_re.match(auth) if m is None: print("no authentication found (%s)" % auth) return print(b"got auth: " + auth) self.name = m.group(1) self.client.send(b"hello, " + self.name + b"\n") extra_data = {} m = extra_data_re.match(buf) if m is not None: extra_data_json = m.group(2) extra_data = json.loads(extra_data_json.decode('utf-8')) buf = buf[len(m.group(0)):] if "geometry" in extra_data: self.handler = Handler(extra_data["geometry"][1], extra_data["geometry"][0]) else: self.handler = Handler(24, 80) self.handler.process(buf) while True: buf = self.client.recv(1024) if len(buf) > 0: self.publisher.notify("new_data", self.connection_id, self.handler.buf, buf) self.handler.process(buf) else: return def msg_new_viewer(self, connection_id): if connection_id != self.connection_id: return self.publisher.notify("new_data", self.connection_id, self.handler.buf, b'') self.client.send(b"msg watcher connected\n") def msg_viewer_disconnect(self, connection_id): self.client.send(b"msg watcher disconnected\n") def request_get_streamers(self): return { "name": self.name, "id": self.connection_id, "rows": self.handler.rows, "cols": self.handler.cols, "idle_time": self.handler.idle_time(), "total_time": self.handler.total_time(), }