from typing import TYPE_CHECKING import asyncio, json, os from quart import ( Blueprint, render_template, send_from_directory, request, websocket, ) if TYPE_CHECKING: from server.Server import Server def create_dashboard_blueprint(server:'Server') -> Blueprint: blueprint = Blueprint('dashboard', __name__) @blueprint.get('/dashboard') async def dashboard_view(): initial_game_id = request.args.get('game_id', '') initial_summary = await server.dashboard_query.get_dashboard_summary() initial_games = await server.dashboard_query.get_dashboard_games(limit=100) return await render_template( 'dashboard.htm', initial_game_id=initial_game_id, initial_summary=initial_summary, initial_games=initial_games, battlesnake_url=os.getenv('BATTLESNAKE_GAMEBOARD_URL', 'https://play.battlesnake.com/game') ) @blueprint.get('/dashboard/customizations/') async def dashboard_customizations_asset(asset_path:str): customization_root = os.path.join( server.app.static_folder, 'customizations', ) return await send_from_directory(customization_root, asset_path) @blueprint.websocket('/dashboard/ws/games') async def dashboard_games_ws(): ws_hub = server.dashboard_ws_hub websocket_task = asyncio.current_task() if websocket_task is not None: await ws_hub.register_task(websocket_task) subscriber_queue:asyncio.Queue[str] = asyncio.Queue(maxsize=20) await ws_hub.register_subscriber(subscriber_queue) try: initial_payload = await server.dashboard_query.build_dashboard_games_event() await asyncio.wait_for( websocket.send(json.dumps(initial_payload)), timeout=1.5 ) while True: queue_task = asyncio.create_task(subscriber_queue.get()) receive_task = asyncio.create_task(websocket.receive()) try: done, _ = await asyncio.wait( {queue_task, receive_task}, timeout=1.0, return_when=asyncio.FIRST_COMPLETED, ) if len(done) == 0: if ws_hub.shutdown_event.is_set(): await asyncio.wait_for( websocket.send(ws_hub.shutdown_message), timeout=1.5, ) break continue if receive_task in done: try: request_payload_raw = receive_task.result() except Exception: break response_event = await server.dashboard_query.handle_dashboard_ws_request(request_payload_raw) if response_event is not None: await asyncio.wait_for( websocket.send(json.dumps(response_event)), timeout=1.5, ) if queue_task in done: event_payload = queue_task.result() if event_payload == ws_hub.shutdown_message: await asyncio.wait_for( websocket.send(event_payload), timeout=1.5 ) break await asyncio.wait_for( websocket.send(event_payload), timeout=1.5 ) except asyncio.TimeoutError: if ws_hub.shutdown_event.is_set(): await asyncio.wait_for( websocket.send(ws_hub.shutdown_message), timeout=1.5, ) break finally: for pending_task in (queue_task, receive_task): if not pending_task.done(): pending_task.cancel() await asyncio.gather( queue_task, receive_task, return_exceptions=True ) except asyncio.CancelledError: pass except Exception: pass finally: await ws_hub.unregister_subscriber(subscriber_queue) if websocket_task is not None: await ws_hub.unregister_task(websocket_task) return blueprint