collab_v2.0.0 / app.py
Ramesh-vani's picture
Update app.py
09cad62 verified
raw
history blame
16 kB
#!/usr/bin/env python
import asyncio
import json
import os
import secrets
import signal
import subprocess
import websockets
import shutil
from connect4 import PLAYER1, PLAYER2, Connect4
from user import User
JOIN = {}
WATCH = {}
async def generate_file_structure(path='.', encoding='utf-8'):
file_structure = {'name': os.path.basename(path), 'type': 'folder', 'path': path, 'children': []}
try:
entries = os.listdir(path)
except FileNotFoundError:
return file_structure # Return an empty structure for non-existing directories
for entry in entries:
entry_path = os.path.join(path, entry)
if os.path.isdir(entry_path):
child_structure =await generate_file_structure(entry_path, encoding)
file_structure['children'].append(child_structure)
elif os.path.isfile(entry_path):
try:
with open(entry_path, 'r', encoding=encoding) as file:
content = file.read()
except UnicodeDecodeError:
content = 'Unable to read content'
file_structure['children'].append({'name': entry, 'type': 'file', 'path': entry_path, 'content': content})
return file_structure
async def rename_item(websocket, key,project_name, path,new_name, connected):
old_path = os.path.join(os.getcwd(),'projects', key,project_name, path)
new_name = new_name
try:
if os.path.exists(old_path):
# Determine the new path
new_path = os.path.join(os.path.dirname(old_path), new_name)
# Rename the file or folder
os.rename(old_path, new_path)
websockets.broadcast(connected,'success')
event = {
"type": "rename-success",
"data": f'{old_path}-->{new_path}',
}
websockets.broadcast(connected,json.dumps(event))
else:
event = {
"type": "rename-failed",
"data": f'{old_path}-->{new_path} failed Item not found',
}
websockets.broadcast(connected,json.dumps(event))
except Exception as e:
websockets.broadcast(connected,str(e))
async def delete_item(websocket, key,project_name, path, connected):
try:
item_path = os.path.join(os.getcwd(), 'projects', key,project_name, path)
if os.path.exists(item_path):
if os.path.isdir(item_path):
shutil.rmtree(item_path) # Remove the directory and its contents
elif os.path.isfile(item_path):
os.remove(item_path) # Remove the file
event = {
"type": "delete-success",
"data": item_path,
}
websockets.broadcast(connected,json.dumps(event))
# websockets.broadcast(connected,'success')
else:
event = {
"type": "delete-failed",
"data": f'{item_path} Item not found',
}
websockets.broadcast(connected,json.dumps(event))
except Exception as e:
# print(e)
websockets.broadcast(connected,str(e))
async def create_file(websocket, key,project_name, path,name, connected):
file_path = os.path.join(os.getcwd(), 'projects', key,project_name, path,name)
# Create the file
with open(file_path, 'w'):
pass
event = {
"type": "file-created",
"data": file_path,
}
websockets.broadcast(connected,json.dumps(event))
async def create_folder(websocket, key,project_name, path,name, connected):
folder_path = os.path.join(os.getcwd(), 'projects', key,project_name, path,name)
# Create the folder
os.makedirs(folder_path)
event = {
"type": "folder-created",
"data": folder_path,
}
websockets.broadcast(connected,json.dumps(event))
async def wirte_file(websocket, key,project_name, path, content, connected):
try:
file_path = os.path.join(os.getcwd(), 'projects', key,project_name, path)
file_content = content
with open(file_path, 'w', encoding='utf-8') as file:
file.write(file_content)
event = {
"type": "write-success",
"data": file_path,
}
websockets.broadcast(connected,json.dumps(event))
except FileNotFoundError as e:
event = {
"type": "write-error",
"data": e,
}
websockets.broadcast(connected,json.dumps(event))
async def execute_command(websocket, key,project_name, command, connected):
base_path = os.path.join(os.getcwd(), 'projects', key,project_name)
mod_command = f'cd {base_path} && {command}'
try:
process = await asyncio.create_subprocess_shell(
mod_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}')
websockets.broadcast(connected,json.dumps(message) )
async for line in process.stdout:
print('sending line')
event = {
"type": "terminal-data",
"data": line.strip().decode('utf-8'),
}
await send_message(event)
async for line in process.stderr:
print(f'error:{line.strip()}')
event = {
"type": "terminal-error",
"data": line.strip().decode('utf-8'),
}
await send_message(event)
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}')
pass
except Exception as e:
await error(websocket, str(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,key):
"""
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"]
print(event)
try:
if event["command"]["type"]=="shell":
await execute_command(websocket, key,event["project_name"], event["command"]["command"], connected)
elif event["command"]["type"]=="write":
await wirte_file(websocket, key,event["project_name"], event["path"], event["content"], connected)
elif event["command"]["type"]=="create":
if event["item"]=="folder":
await create_folder(websocket, key,event["project_name"], event["path"],event["name"], connected)
else:
await create_file(websocket, key,event["project_name"], event["path"],event["name"], connected)
elif event["command"]["type"]=="delete":
await delete_item(websocket, key,event["project_name"], event["path"], connected)
elif event["command"]["type"]=="rename":
await rename_item(websocket, key,event["project_name"], event["path"], event["name"], connected)
elif event["command"]["type"]=="collabration":
event = {
"type": "collabration",
'name': event["name"],
'line': event["cursorPos-line"],
'ch': event["cursorPos-ch"],
'content': event["content"],
}
websockets.broadcast(connected, json.dumps(event))
else:
# First player starts a new game.
pass
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',join_key, 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,join_key)
finally:
del JOIN[join_key]
del WATCH[watch_key]
async def join(websocket, key):
"""
Handle a connection from the second player: join an existing game.
"""
# Find the Connect Four game.
try:
game, connected = JOIN[key]
except KeyError:
await error(websocket, "collabration not found.")
return
# Register to receive moves from this game.
connected.add(websocket)
try:
current_directory = f'projects/{key}/'
# Send the first move, in case the first player already played it.
file_structure = await generate_file_structure(current_directory)
# Receive and process moves from the second player.
event = {
"type": "join",
"file_structure": json.dumps(file_structure),
}
await websocket.send(json.dumps(event))
await exe(websocket,connected,key)
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())