collab_v2.0.0 / app.py
Ramesh-vani's picture
Update app.py
c305bbf verified
raw
history blame
23.1 kB
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
import requests
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class FileHandler(FileSystemEventHandler):
def on_modified(self, event):
if event.is_directory:
return
print(f'File {event.src_path} has been modified.')
def on_created(self, event):
if event.is_directory:
return
print(f'File {event.src_path} has been created.')
def on_deleted(self, event):
if event.is_directory:
return
print(f'File {event.src_path} has been deleted.')
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,rpath,new_name,root, connected):
old_path = os.path.join(os.getcwd(),'projects', key,project_name, rpath)
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}',
"path": path,
"new_rpath":rpath,
"name": new_name,
"root":root,
}
websockets.broadcast(connected,json.dumps(event))
else:
event = {
"type": "rename-failed",
"data": f'{old_path}-->{new_path} failed Item not found',
"old_path": path,
"new_name": new_name,
}
websockets.broadcast(connected,json.dumps(event))
except Exception as e:
websockets.broadcast(connected,str(e))
async def delete_item(websocket, key,project_name, path,rpath,targetElementData, connected):
try:
item_path = os.path.join(os.getcwd(), 'projects', key,project_name, rpath)
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": path,
"path":path,
"targetElementData":targetElementData,
}
print(event)
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 get_file_content(websocket, key,project_name, path,rpath,name,connected):
file_path = os.path.join(os.getcwd(), 'projects', key,project_name, rpath)
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
event = {
"type": "content",
"content": content,
'fileName':name,
'rfilePath':rpath,
'filePath':path,
}
await websocket.send(json.dumps(event))
except Exception as e:
event = {
"type": "error",
"message": f"Failed to read file content: {str(e)}",
}
await websocket.send(json.dumps(event))
async def create_file(websocket, key,project_name, path,name,root,targetElementData,rpath, connected):
file_path = os.path.join(os.getcwd(), 'projects', key,project_name, rpath,name)
# Create the file
with open(file_path, 'w'):
pass
event = {
"type": "file-created",
"data": file_path,
"path": path,
"name": name,
"root":root,
"targetElementData":targetElementData,
}
websockets.broadcast(connected,json.dumps(event))
async def create_folder(websocket, key,project_name, path,name,root,targetElementData,rpath, connected):
folder_path = os.path.join(os.getcwd(), 'projects', key,project_name, rpath,name)
# Create the folder
os.makedirs(folder_path)
event = {
"type": "folder-created",
"data": folder_path,
"path": path,
"name": name,
"root":root,
"targetElementData":targetElementData,
}
print(folder_path,'created')
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,
"path": path,
"content": content,
}
# 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.
print(message)
event = json.loads(message)
assert event["type"] == "cmd"
# command = event["command"]
print(event)
try:
if event["command"]["type"]=="shell":
base_path = os.path.join(os.getcwd(), 'projects', key,event["project_name"])
directory_path = base_path
event_handler = FileHandler()
observer = Observer()
observer.schedule(event_handler, path=directory_path, recursive=True)
observer.start()
try:
await execute_command(websocket, key,event["project_name"], event["command"]["command"], connected)
except KeyboardInterrupt:
pass # Handle KeyboardInterrupt to gracefully stop the observer
observer.stop()
observer.join()
elif event["command"]["type"]=="write":
await wirte_file(websocket, key,event["project_name"], event["path"], event["content"], connected)
elif event["command"]["type"]=="curl":
response = requests.get(event['url'])
event = {
"type": "web-data",
"data": response.text,
}
await websocket.send(json.dumps(event))
elif event["command"]["type"]=="create":
if event["item"]=="folder":
await create_folder(websocket, key,event["project_name"], event["path"],event["name"],event['root'],event['targetElementData'],event["rpath"], connected)
else:
await create_file(websocket, key,event["project_name"], event["path"],event["name"],event['root'],event['targetElementData'],event["rpath"], connected)
elif event["command"]["type"]=="delete":
await delete_item(websocket, key,event["project_name"], event["path"],event['rpath'],event['targetElementData'], connected)
elif event["command"]["type"]=="get_content":
await get_file_content(websocket, key,event["project_name"],event["filePath"], event["rfilePath"],event["fileName"] ,connected)
elif event["command"]["type"]=="rename":
await rename_item(websocket, key,event["project_name"], event["path"],event['rpath'], event["name"], event["root"], connected)
elif event["command"]["type"]=="join":
await join(websocket, event["join"])
elif event["command"]["type"]=="sendDir":
data=json.loads(event["file_structure"])
event = {
"type": "createDir",
"path": data,
"root":event['root'],
}
# websockets.broadcast(connected,json.dumps(event))
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="createItemUI":
event = {
"type": "createItemUI",
'targetElementData':event['targetElementData'],
'data':event['data']
}
# websockets.broadcast(connected,json.dumps(event))
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="renameItemInUI":
event = {
"type": "renameItemInUI",
'path':event['path'],
'new_path':event['new_path'],
'name':event['name'],
'new_rpath':event['new_rpath'],
}
# websockets.broadcast(connected,json.dumps(event))
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="createFolderUI":
event = {
"type": "createFolderUI",
'targetElementData':event['targetElementData'],
'data':event['data']
}
# websockets.broadcast(connected,json.dumps(event))
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="removeItemFromUI":
event = {
"type": "removeItemFromUI",
'targetElementData':event['targetElementData'],
'path':event['path']
}
# websockets.broadcast(connected,json.dumps(event))
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="project":
base_path = os.path.join(os.getcwd(), 'projects',key, event["project_name"])
data=json.loads(event["file_structure"])
await create_file_structure(websocket,data, base_path=base_path)
# 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"],
'file':event["file"],
'content': event["content"],
'color':event["color"]
}
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:
# base_path = os.path.join(os.getcwd(), 'projects', key,'your_project_name')
# mod_command = f'cd {base_path} && ls'
# 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,
# )
# project_name = []
# async for line in process.stdout:
# project_name.append(line.strip().decode('utf-8'))
# return_code = await process.wait()
# current_directory = f'projects/{key}/your_project_name/{project_name[0]}'
# print(current_directory)
# print("printed") # 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),
# }
# print('in join')
# print(event)
# await websocket.send(json.dumps(event))
event = {
"type": "sendDir",
}
websockets.broadcast(connected,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())