Use Tornado (#14)

This commit converts the server to use Tornado. Unfortunately, I had to
convert part of the code to async, but I avoided it as much as possible.

Closes #7
This commit is contained in:
Samuel Sloniker 2021-06-20 15:34:04 -07:00 committed by GitHub
parent 8155adb853
commit 1624ee8996
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -1,14 +1,14 @@
import os import os
import base64 import base64
import threading import threading
import logging
import random import random
import time import time
import shutil import shutil
import tempfile import tempfile
from websocket_server import WebsocketServer import tornado.web, tornado.websocket, tornado.ioloop
import imgproc as hcapi import imgproc as hcapi
import argon2 import argon2
import asyncio
# Select backend - backends.port8080 uses HamClock's port 8080 service; # Select backend - backends.port8080 uses HamClock's port 8080 service;
# backends.x11 uses an X11 server (typically Xvfb) (make sure DISPLAY is set # backends.x11 uses an X11 server (typically Xvfb) (make sure DISPLAY is set
@ -19,15 +19,15 @@ import backends.x11 as backend
hcapi.backend = backend hcapi.backend = backend
ph = argon2.PasswordHasher() ph = argon2.PasswordHasher()
client = None
def cycle(): def cycle():
try: try:
changed = hcapi.get_split_imgs() changed = hcapi.get_split_imgs()
except Exception as e: except Exception as e:
for client in clients.values(): if client is not None:
client.send('err%noconn%Server failed to capture screenshot', 'ERR') client.send('err%noconn%Server failed to capture screenshot', 'ERR')
time.sleep(3) time.sleep(3)
return return
@ -35,50 +35,12 @@ def cycle():
for i in changed: for i in changed:
threading.Thread(target=do_img, args=(i,)).start() threading.Thread(target=do_img, args=(i,)).start()
for client in clients.values(): if client is not None:
client.ack() client.ack()
class Client:
def __init__(self, client, server):
self.client = client
self.server = server
self.items = {}
self.lock = threading.Lock()
self.good = True
def send(self, item, name):
with self.lock:
self.items[name] = item
def ack(self):
with self.lock:
try:
self.server.send_message(self.client, 'ack')
self.good = False
except BrokenPipeError:
pass
def cycle(self):
with self.lock:
try:
while not self.good:
pass
for name, item in list(self.items.items()):
if not self.good:
break
self.server.send_message(self.client, item)
del self.items[name]
except BrokenPipeError:
pass
def run(self):
while True:
self.cycle()
clients = {}
def do_img(imgname): def do_img(imgname):
for client in clients.values(): if client is not None:
client.send(img(imgname), imgname) client.send(img(imgname), imgname)
@ -90,33 +52,68 @@ def img(imgname):
return response return response
def new_client(client, server): class HCRAServer(tornado.websocket.WebSocketHandler):
if clients: def open(self):
server.send_message(client, 'err%*inuse%Server already in use') global client
client['handler'].send_text("", opcode=0x8) if client is not None:
return self.write_message('err%*inuse%Server already in use')
self.close()
clients[client['id']] = Client(client, server) else:
client = self
self.items = {}
self.lock = threading.Lock()
self.good = True
try: try:
imgname = hcapi.get_full_img() imgname = hcapi.get_full_img()
except Exceptidon as e: except Exception as e:
server.send_message(client, 'err%noconn%Server failed to capture screenshot') self.write_message('err%noconn%Server failed to capture screenshot')
return return
clients[client['id']] = Client(client, server)
with open(imgname, 'rb') as f: with open(imgname, 'rb') as f:
img = f.read() img = f.read()
os.unlink(imgname) os.unlink(imgname)
server.send_message(client, f'pic%0x0%data:imgage/jpeg;base64,{base64.b64encode(img).decode("utf-8")}') self.write_message(f'pic%0x0%data:imgage/jpeg;base64,{base64.b64encode(img).decode("utf-8")}')
clients[client['id']] = Client(client, server) self.ack()
clients[client['id']].ack() loop = asyncio.new_event_loop()
threading.Thread(target=clients[client['id']].run).start() threading.Thread(target=self.run, args=(loop,)).start()
def do_touch(client, server, message): def send(self, item, name):
with self.lock:
self.items[name] = item
def ack(self):
with self.lock:
self.items['ack'] = 'ack'
async def cycle(self):
with self.lock:
while not self.good:
pass
for name, item in list(self.items.items()):
if not self.good:
break
self.write_message(item)
del self.items[name]
if item == 'ack':
self.good = False
def run(self, loop):
asyncio.set_event_loop(loop)
while True:
if client is not self:
break
loop.run_until_complete(self.cycle())
def on_close(self):
global client
if client is self:
self.good = None
client = None
def on_message(self, message):
action = message.split(' ', 1)[0] action = message.split(' ', 1)[0]
if action == 'ack': if action == 'ack':
clients[client['id']].good = True self.good = True
else: else:
_, password, x, y, w, is_long = message.split(' ') _, password, x, y, w, is_long = message.split(' ')
try: try:
@ -124,16 +121,16 @@ def do_touch(client, server, message):
x, y, w, is_long = int(x), int(y), int(w), is_long == 'true' x, y, w, is_long = int(x), int(y), int(w), is_long == 'true'
hcapi.touch(x, y, w, is_long) hcapi.touch(x, y, w, is_long)
except argon2.exceptions.VerifyMismatchError: except argon2.exceptions.VerifyMismatchError:
clients[client['id']].send(f'badpass', 'BADPASS') self.send(f'err%*badpass%Incorrect password', 'BADPASS')
client['handler'].send_text("", opcode=0x8) self.close()
def check_origin(self, origin):
return True
def client_left(client, server):
clients[client['id']].good = False
del clients[client['id']]
def do_cycles(): def do_cycles():
while True: while True:
if clients: if client is not None:
cycle() cycle()
time.sleep(0.4) time.sleep(0.4)
else: else:
@ -144,12 +141,14 @@ try:
shutil.copy('crops.json', tmp) shutil.copy('crops.json', tmp)
os.chdir(tmp) os.chdir(tmp)
os.mkdir('pieces') os.mkdir('pieces')
server = WebsocketServer(1234, host='0.0.0.0', loglevel=logging.INFO)
threading.Thread(target=do_cycles).start() threading.Thread(target=do_cycles).start()
server.set_fn_new_client(new_client)
server.set_fn_message_received(do_touch) application = tornado.web.Application([
server.set_fn_client_left(client_left) (r"/", HCRAServer),
server.run_forever() ])
application.listen(1234)
tornado.ioloop.IOLoop.current().start()
finally: finally:
os.chdir('/') os.chdir('/')
shutil.rmtree(tmp) shutil.rmtree(tmp)