Spaces:
Running
Running
#!/usr/bin/env python | |
import asyncio | |
import json | |
import os | |
import secrets | |
import signal | |
import subprocess | |
import websockets | |
from connect4 import PLAYER1, PLAYER2, Connect4 | |
from user import User | |
JOIN = {} | |
WATCH = {} | |
async def execute_command(websocket, project_name, command): | |
base_path = os.path.join(os.getcwd(), 'projects', project_name) | |
try: | |
process = await asyncio.create_subprocess_shell( | |
command, | |
cwd=base_path, | |
stdin=asyncio.subprocess.PIPE, | |
stdout=asyncio.subprocess.PIPE, | |
stderr=asyncio.subprocess.PIPE, | |
# text=True, | |
) | |
async def send_message(message): | |
print('sending msg') | |
await websocket.send(f'data: {message}') | |
async for line in process.stdout: | |
print('sending line') | |
print(line.strip()) | |
# await process_input_request(line) | |
await send_message(line.strip()) | |
print(line.strip()) | |
async for line in process.stderr: | |
print(f'error:{line.strip()}') | |
await send_message(f'error:{line.strip()}') | |
return_code = await process.wait() | |
if return_code == 0: | |
await send_message('Code executed successfully') | |
# pass | |
else: | |
await send_message(f'error:Execution failed with return code {return_code}') | |
# Send the command output to the client | |
# output, error = process.communicate() | |
# response = f"Command executed:\n\nOutput:\n{output.decode('utf-8')}\nError:\n{error.decode('utf-8')}" | |
# await websocket.send(response) | |
except Exception as e: | |
await websocket.send(e) | |
async def create_file_structure(websocket, data, base_path='.'): | |
if data['type'] == 'folder': | |
folder_path = os.path.join(base_path, data['name']) | |
os.makedirs(folder_path, exist_ok=True) | |
for child in data['children']: | |
await create_file_structure(websocket,child, base_path=folder_path) | |
elif data['type'] == 'file': | |
file_path = os.path.join(base_path, data['name']) | |
with open(file_path, 'w', encoding='utf-8') as file: | |
file.write(data['content']) | |
event = { | |
"type": "msg", | |
"message": "project created", | |
} | |
await websocket.send(json.dumps(event)) | |
async def error(websocket, message): | |
""" | |
Send an error message. | |
""" | |
event = { | |
"type": "error", | |
"message": message, | |
} | |
await websocket.send(json.dumps(event)) | |
async def replay(websocket, game): | |
""" | |
Send previous moves. | |
""" | |
# Make a copy to avoid an exception if game.moves changes while iteration | |
# is in progress. If a move is played while replay is running, moves will | |
# be sent out of order but each move will be sent once and eventually the | |
# UI will be consistent. | |
for player, column, row in game.moves.copy(): | |
event = { | |
"type": "play", | |
"player": player, | |
"column": column, | |
"row": row, | |
} | |
await websocket.send(json.dumps(event)) | |
async def play(websocket, game, player, connected): | |
""" | |
Receive and process moves from a player. | |
""" | |
async for message in websocket: | |
# Parse a "play" event from the UI. | |
event = json.loads(message) | |
assert event["type"] == "play" | |
column = event["column"] | |
try: | |
# Play the move. | |
row = game.play(player, column) | |
except RuntimeError as exc: | |
# Send an "error" event if the move was illegal. | |
await error(websocket, str(exc)) | |
continue | |
# Send a "play" event to update the UI. | |
event = { | |
"type": "play", | |
"player": player, | |
"column": column, | |
"row": row, | |
} | |
websockets.broadcast(connected, json.dumps(event)) | |
# If move is winning, send a "win" event. | |
if game.winner is not None: | |
event = { | |
"type": "win", | |
"player": game.winner, | |
} | |
websockets.broadcast(connected, json.dumps(event)) | |
async def exe(websocket,connected): | |
""" | |
Receive and process moves from a player. | |
""" | |
print('in exe') | |
async for message in websocket: | |
# Parse a "play" event from the UI. | |
event = json.loads(message) | |
assert event["type"] == "cmd" | |
# command = event["command"] | |
try: | |
if "command" in event: | |
await execute_command(websocket, event["project_name"], event["command"]) | |
elif "write" in event: | |
# Spectator watches an existing game. | |
await watch(websocket, event["watch"]) #Todo | |
else: | |
# First player starts a new game. | |
await start(websocket, message) | |
except RuntimeError as exc: | |
# Send an "error" event if the move was illegal. | |
await error(websocket, str(exc)) | |
continue | |
# # Send a "play" event to update the UI. | |
# event = { | |
# "type": "play", | |
# "player": player, | |
# "column": column, | |
# "row": row, | |
# } | |
# websockets.broadcast(connected, json.dumps(event)) | |
# # If move is winning, send a "win" event. | |
# if game.winner is not None: | |
# event = { | |
# "type": "win", | |
# "player": game.winner, | |
# } | |
# websockets.broadcast(connected, json.dumps(event)) | |
async def start(websocket,events): | |
""" | |
Handle a connection from the first player: start a new game. | |
""" | |
# Initialize a Connect Four game, the set of WebSocket connections | |
# receiving moves from this game, and secret access tokens. | |
game = User() | |
connected = {websocket} | |
join_key = secrets.token_urlsafe(12) | |
JOIN[join_key] = game, connected | |
watch_key = secrets.token_urlsafe(12) | |
WATCH[watch_key] = game, connected | |
try: | |
# Send the secret access tokens to the browser of the first player, | |
# where they'll be used for building "join" and "watch" links. | |
event = { | |
"type": "init", | |
"join": join_key, | |
"watch": watch_key, | |
} | |
await websocket.send(json.dumps(event)) | |
js = json.loads(events) | |
assert js["type"] == "init" | |
base_path = os.path.join(os.getcwd(), 'projects', js["project_name"]) | |
data=json.loads(js["file_structure"]) | |
# Receive and process moves from the first player. | |
# await play(websocket, game, PLAYER1, connected) | |
await create_file_structure(websocket,data, base_path=base_path) | |
await exe(websocket,connected) | |
finally: | |
del JOIN[join_key] | |
del WATCH[watch_key] | |
async def join(websocket, join_key): | |
""" | |
Handle a connection from the second player: join an existing game. | |
""" | |
# Find the Connect Four game. | |
try: | |
game, connected = JOIN[join_key] | |
except KeyError: | |
await error(websocket, "Game not found.") | |
return | |
# Register to receive moves from this game. | |
connected.add(websocket) | |
try: | |
# Send the first move, in case the first player already played it. | |
await replay(websocket, game) | |
# Receive and process moves from the second player. | |
await play(websocket, game, PLAYER2, connected) | |
finally: | |
connected.remove(websocket) | |
async def watch(websocket, watch_key): | |
""" | |
Handle a connection from a spectator: watch an existing game. | |
""" | |
# Find the Connect Four game. | |
try: | |
game, connected = WATCH[watch_key] | |
except KeyError: | |
await error(websocket, "Game not found.") | |
return | |
# Register to receive moves from this game. | |
connected.add(websocket) | |
try: | |
# Send previous moves, in case the game already started. | |
await replay(websocket, game) | |
# Keep the connection open, but don't receive any messages. | |
await websocket.wait_closed() | |
finally: | |
connected.remove(websocket) | |
async def handler(websocket): | |
""" | |
Handle a connection and dispatch it according to who is connecting. | |
""" | |
# Receive and parse the "init" event from the UI. | |
message = await websocket.recv() | |
event = json.loads(message) | |
print(event) | |
project_name = event["project_name"] | |
# assert event["type"] == "init" | |
if event["type"] == "init": | |
if "join" in event: | |
# Second player joins an existing game. | |
await join(websocket, event["join"]) | |
elif "watch" in event: | |
# Spectator watches an existing game. | |
await watch(websocket, event["watch"]) | |
else: | |
# First player starts a new game. | |
await start(websocket, message) | |
elif event["type"] == "cmd": | |
print('executing commad') | |
# Execute a command in the project folder. | |
await execute_command(websocket, event["project_name"], event["command"]) | |
# assert event["type"] == "cmd" | |
# if "command" in event: | |
# # Second player joins an existing game. | |
# await execute_command(websocket, project_name, event["command"]) | |
# elif "watch" in event: | |
# # Spectator watches an existing game. | |
# await watch(websocket, event["watch"]) | |
# else: | |
# # First player starts a new game. | |
# await start(websocket, message) | |
async def main(): | |
# Set the stop condition when receiving SIGTERM. | |
loop = asyncio.get_running_loop() | |
stop = loop.create_future() | |
loop.add_signal_handler(signal.SIGTERM, stop.set_result, None) | |
port = int(os.environ.get("PORT", "7860")) | |
async with websockets.serve(handler, "0.0.0.0", port): | |
await stop | |
if __name__ == "__main__": | |
asyncio.run(main()) |