From d75ca91bbe2d4fb64cac1b993b4201ad2e3544b5 Mon Sep 17 00:00:00 2001 From: Andrew Lorimer Date: Sat, 8 May 2021 13:51:35 +1000 Subject: [PATCH] replace client-triggered state refresh with server-triggered --- ppt_control/ppt_control.py | 97 +++++++++++++++---------------- ppt_control/ppt_control_obs.py | 3 +- ppt_control/static/ppt-control.js | 56 ++++++++---------- 3 files changed, 75 insertions(+), 81 deletions(-) diff --git a/ppt_control/ppt_control.py b/ppt_control/ppt_control.py index 6b0f818..f942b32 100755 --- a/ppt_control/ppt_control.py +++ b/ppt_control/ppt_control.py @@ -115,11 +115,7 @@ def state_event(): return json.dumps({"type": "state", **STATE}) -def users_event(): - return json.dumps({"type": "users", "count": len(USERS)}) - - -async def notify_state(): +def notify_state(): global STATE if current_slideshow and STATE["connected"] == 1: try: @@ -134,85 +130,86 @@ async def notify_state(): STATE = copy(STATE_DEFAULT) if USERS: # asyncio.wait doesn't accept an empty list message = state_event() - await asyncio.wait([user.send(message) for user in USERS]) - - -async def notify_users(): - if USERS: # asyncio.wait doesn't accept an empty list - message = users_event() - await asyncio.wait([user.send(message) for user in USERS]) - - -async def register(websocket): - USERS.add(websocket) - await notify_users() + loop.call_soon_threadsafe(ws_queue.put_nowait, state_event()) -async def unregister(websocket): - USERS.remove(websocket) - await notify_users() +async def ws_handler(websocket, path): + logger.debug("Handling WebSocket connection") + recv_task = asyncio.ensure_future(ws_receive(websocket, path)) + send_task = asyncio.ensure_future(ws_send(websocket, path)) + done, pending = await asyncio.wait( + [recv_task, send_task], + return_when=asyncio.FIRST_COMPLETED, + ) + for task in pending: + task.cancel() -async def ws_handle(websocket, path): +async def ws_receive(websocket, path): logger.debug("Received websocket request") - global current_slideshow - # register(websocket) sends user_event() to websocket - await register(websocket) + USERS.add(websocket) try: - await websocket.send(state_event()) + # Send initial state to clients on load + notify_state() async for message in websocket: + logger.debug("Received websocket message: " + str(message)) data = json.loads(message) if data["action"] == "prev": if current_slideshow: current_slideshow.prev() - await notify_state() + notify_state() elif data["action"] == "next": if current_slideshow: current_slideshow.next() - await notify_state() + notify_state() elif data["action"] == "first": if current_slideshow: current_slideshow.first() - await notify_state() + notify_state() elif data["action"] == "last": if current_slideshow: current_slideshow.last() - await notify_state() + notify_state() elif data["action"] == "black": if current_slideshow: if current_slideshow.visible() == 3: current_slideshow.normal() else: current_slideshow.black() - await notify_state() + notify_state() elif data["action"] == "white": if current_slideshow: if current_slideshow.visible() == 4: current_slideshow.normal() else: current_slideshow.white() - await notify_state() + notify_state() elif data["action"] == "goto": if current_slideshow: current_slideshow.goto(int(data["value"])) - await notify_state() - elif data["action"] == "refresh": - await notify_state() - if current_slideshow: - current_slideshow.export_current_next() - current_slideshow.refresh() + notify_state() else: - logger.error("unsupported event: {}", data) + logger.error("Received unnsupported event: {}", data) + message = "" finally: - await unregister(websocket) + USERS.remove(websocket) + +async def ws_send(websocket, path): + while True: + message = await ws_queue.get() + await asyncio.wait([user.send(message) for user in USERS]) + def run_ws(): # https://stackoverflow.com/questions/21141217/how-to-launch-win32-applications-in-separate-threads-in-python/22619084#22619084 # https://www.reddit.com/r/learnpython/comments/mwt4qi/pywintypescom_error_2147417842_the_application/ pythoncom.CoInitializeEx(pythoncom.COINIT_MULTITHREADED) asyncio.set_event_loop(asyncio.new_event_loop()) - #start_server = websockets.serve(ws_handle, "0.0.0.0", 5678, ping_interval=None) - start_server = websockets.serve(ws_handle, "0.0.0.0", 5678) + global ws_queue + ws_queue = asyncio.Queue() + global loop + loop = asyncio.get_event_loop() + start_server = websockets.serve(ws_handler, "0.0.0.0", 5678, ping_interval=None) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever() @@ -258,7 +255,6 @@ class Slideshow: connect_ppt() def refresh(self): - logger.debug("Refreshing") try: if self.instance is None: raise ValueError("PPT instance cannot be None") @@ -388,7 +384,10 @@ class Slideshow: pass attempts += 1 elif slide == self.total_slides() + 1: - shutil.copyfileobj(open(os.path.dirname(os.path.realpath(__file__)), r'''\static\black.jpg''', 'rb'), open(destination, 'wb')) + try: + shutil.copyfileobj(open(os.path.dirname(os.path.realpath(__file__)), r'''\static\black.jpg''', 'rb'), open(destination, 'wb')) + except Exception as e: + logger.warning("Failed to copy black slide: " + str(e)) else: pass @@ -407,10 +406,10 @@ def get_current_slideshow(): def refresh_interval(): while getattr(refresh_daemon, "do_run", True): - logger.debug("Triggering server-side refresh") current_slideshow.refresh() + notify_state() refresh_status() - time.sleep(1) + time.sleep(0.5) def refresh_status(): if status_label is not None: @@ -564,10 +563,10 @@ def start_interface(): console_handler.setLevel(log_level) logger.addHandler(console_handler) - logging.getLogger("asyncio").setLevel(logging.ERROR) - logging.getLogger("asyncio.coroutines").setLevel(logging.ERROR) - logging.getLogger("websockets.server").setLevel(logging.ERROR) - logging.getLogger("websockets.protocol").setLevel(logging.ERROR) + #logging.getLogger("asyncio").setLevel(logging.ERROR) + #logging.getLogger("asyncio.coroutines").setLevel(logging.ERROR) + #logging.getLogger("websockets.server").setLevel(logging.ERROR) + #logging.getLogger("websockets.protocol").setLevel(logging.ERROR) logger.debug("Finished setting up config and logging") diff --git a/ppt_control/ppt_control_obs.py b/ppt_control/ppt_control_obs.py index e36957e..aa2e44b 100755 --- a/ppt_control/ppt_control_obs.py +++ b/ppt_control/ppt_control_obs.py @@ -44,8 +44,9 @@ async def communicate(): while True: if cmd: try: - await websocket.send('{"action": "%s"}' % cmd) + cmd_temp = cmd cmd = "" + await websocket.send('{"action": "%s"}' % cmd_temp) except websockets.ConnectionClosed as exc: attempts += 1 if attempts == 4: diff --git a/ppt_control/static/ppt-control.js b/ppt_control/static/ppt-control.js index 9bba171..2265ca3 100644 --- a/ppt_control/static/ppt-control.js +++ b/ppt_control/static/ppt-control.js @@ -172,8 +172,13 @@ function disconnect() { users.textContent = "Connection to PowerPoint failed"; } +function update_current() { +} + +function update_next() { +} + websocket.onmessage = function (event) { - console.log("Received data"); data = JSON.parse(event.data); switch (data.type) { case 'state': @@ -183,40 +188,37 @@ websocket.onmessage = function (event) { break; } var d = new Date; - switch (data.visible) { - case 3: - current_img.src = "/black.jpg"; - break; - case 4: - current_img.src = "/white.jpg"; - break; - default: - //current_img.src = "/cache/" + data.current + ".jpg?t=" + d.getTime(); - current_img.src = "/cache/" + data.current + ".jpg"; - break; + if (show_current.checked) { + switch (data.visible) { + case 3: + current_img.src = "/black.jpg"; + break; + case 4: + current_img.src = "/white.jpg"; + break; + default: + current_img.src = "/cache/" + data.current + ".jpg?t=" + d.getTime(); + //current_img.src = "/cache/" + data.current + ".jpg"; + break; + } } if (data.current == data.total + 1) { - //next_img.src = "/cache/" + (data.total + 1) + ".jpg?t=" + d.getTime(); - next_img.src = "/cache/" + (data.total + 1) + ".jpg"; + next_img.src = "/cache/" + (data.total + 1) + ".jpg?t=" + d.getTime(); + //next_img.src = "/cache/" + (data.total + 1) + ".jpg"; } else { - //next_img.src = "/cache/" + (data.current + 1) + ".jpg?t=" + d.getTime(); - next_img.src = "/cache/" + (data.current + 1) + ".jpg"; + next_img.src = "/cache/" + (data.current + 1) + ".jpg?t=" + d.getTime(); + //next_img.src = "/cache/" + (data.current + 1) + ".jpg"; } - if (document.activeElement != current) { + if (document.activeElement != current) { current.value = data.current; } total.textContent = data.total; document.title = data.name; break; - case 'users': - users.textContent = ( - data.count.toString() + " client" + - (data.count == 1 ? "" : "s")); - break; default: console.error( - "unsupported event", data); + "Unsupported event", data); } if (preloaded == false && ! isNaN(total.textContent)) { image = document.getElementById("preload_img"); @@ -230,11 +232,3 @@ websocket.onmessage = function (event) { } }; - -var interval = setInterval(refresh, 1000); - -function refresh() { - console.log("Refreshing") - websocket.send(JSON.stringify({action: 'refresh'})); -} - -- 2.43.2