Server / server.py
Ramesh-vani's picture
Update server.py
f05f654 verified
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
__all__ = ["User",]
class User:
"""
A Connect user .
"""
def __init__(self):
pass
import requests
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class FileHandler(FileSystemEventHandler):
def __init__(self, websocket,connected):
super().__init__()
self.websocket = websocket
self.connected = connected
def on_modified(self, event):
if event.is_directory:
return
with open(event.src_path, 'r', encoding='utf-8') as file:
content = file.read()
# print(f'File {event.src_path} has been modified.')
new_event = {
"type": "file-modifie",
"content": content,
"path": event.src_path,
}
websockets.broadcast(self.connected,json.dumps(new_event))
# websockets.broadcast(self.connected,f'File {event.src_path} has been modified.')
def on_created(self, event):
if event.is_directory:
new_event = {
"type": "folder-create",
"path": event.src_path,
"name": event.src_path.split('/')[-1]
}
# print(new_event)
websockets.broadcast(self.connected,json.dumps(new_event))
else:
with open(event.src_path, 'r', encoding='utf-8') as file:
content = file.read()
# print(f'File {event.src_path} has been created.')
new_event = {
"type": "file-create",
"content": content,
"path": event.src_path,
"name": event.src_path.split('/')[-1]
}
# print(new_event)
websockets.broadcast(self.connected,json.dumps(new_event))
# websockets.broadcast(self.connected,f'File {event.src_path} has been created.')
def on_deleted(self, event):
if event.is_directory:
new_event = {
"type": "delete",
"path": event.src_path,
"name": event.src_path.split('/')[-1],
}
websockets.broadcast(self.connected,json.dumps(new_event))
else:
# print(f'File {event.src_path} has been deleted.')
new_event = {
"type": "delete",
"name": event.src_path.split('/')[-1],
"path": event.src_path,
}
websockets.broadcast(self.connected,json.dumps(new_event))
def on_moved(self, event):
if event.is_directory:
new_event = {
"type": "rename",
"OldPath": event.src_path,
"oldname": event.src_path.split('/')[-1],
"NewPath": event.dest_path,
"newname": event.dest_path.split('/')[-1],
}
websockets.broadcast(self.connected,json.dumps(new_event))
else:
# print(f'File {event.src_path} has been renamed to {event.dest_path}.')
new_event = {
"type": "rename",
"OldPath": event.src_path,
"oldname": event.src_path.split('/')[-1],
"NewPath": event.dest_path,
"newname": event.dest_path.split('/')[-1],
}
websockets.broadcast(self.connected,json.dumps(new_event))
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 read_process_output(process, websocket):
async for line in process.stdout:
# print('sending line')
event = {
"type": "terminal-data",
"data": line.strip().decode('utf-8'),
}
await websocket.send(json.dumps(event))
async for line in process.stderr:
# print(f'error:{line.strip()}')
event = {
"type": "terminal-error",
"data": line.strip().decode('utf-8'),
}
await websocket.send(json.dumps(event))
async def handle_user_input(websocket,key, process, connected):
while True:
try:
await asyncio.sleep(0.1)
if process.returncode is not None:
break
# message = await websocket.recv()
async for message in websocket:
# user_input = json.loads(message)
# print(user_input)
# print(f'Received user input: {user_input["command"]["command"]}')
# process_input(user_input["command"]["command"], process)
event = json.loads(message)
assert event["type"] == "cmd"
# command = event["command"]
# print(f'Received user input: {event}')
try:
if event["command"]["type"]=="shell":
await asyncio.sleep(0.1)
if process.returncode is not None:
base_path = os.path.join(os.getcwd(), 'projects', key,event["project_name"])
directory_path = base_path
event_handler = FileHandler(websocket,connected)
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)
# base_path = os.path.join(os.getcwd(), 'projects', key,event["project_name"])
print(base_path)
mod_command = f'cd {base_path} && {event["command"]["command"]}'
print(mod_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) )
await asyncio.gather(
handle_user_input(websocket,key, process, connected),
read_process_output(process, websocket)
)
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))
except KeyboardInterrupt:
pass # Handle KeyboardInterrupt to gracefully stop the observer
observer.stop()
observer.join()
else:
process_input(event["command"]["command"], process)
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))
elif event["command"]["type"]=="cursor":
event = {
"type": "cursor",
'offset': event["offset"],
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
'fileName':event["fileName"],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="select":
event = {
"type": "select",
'id': event["id"],
'startOffset': event["startOffset"],
'endOffset': event["endOffset"],
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
'fileName':event["fileName"],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="addselection":
event = {
"type": "addselection",
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="insert":
event = {
"type": "insert",
'index': event["index"],
'text': event["text"],
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
'fileName':event["fileName"],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="replace":
event = {
"type": "replace",
'index': event["index"],
'length': event["length"],
'text': event["text"],
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
'fileName':event["fileName"],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="edelelte":
event = {
"type": "edelelte",
'index': event["index"],
'length': event["length"],
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
'fileName':event["fileName"],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="keep-alive":
event = {
"type": "keep-alive",
}
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
break
except websockets.ConnectionClosed:
# print("WebSocket connection closed")
break
except Exception as e:
# print(f"Error in input thread: {str(e)}")
pass
def process_input(user_input, process):
if process:
try:
if user_input=='Ctrl+C':
print('process stoping')
process.send_signal(signal.SIGINT)
else:
process.stdin.write(user_input.encode('utf-8') + b'\n')
#process.stdin.flush()
except Exception as e:
# print(f"Error writing to process stdin: {str(e)}")
pass
else:
# print("No process available to write to.")
pass
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 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(websocket,connected)
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)
# base_path = os.path.join(os.getcwd(), 'projects', key,event["project_name"])
print(base_path)
mod_command = f'cd {base_path} && {event["command"]["command"]}'
print(mod_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) )
await asyncio.gather(
handle_user_input(websocket,key, process, connected),
read_process_output(process, websocket)
)
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))
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))
elif event["command"]["type"]=="cursor":
event = {
"type": "cursor",
'offset': event["offset"],
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
'fileName':event["fileName"],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="select":
event = {
"type": "select",
'id': event["id"],
'startOffset': event["startOffset"],
'endOffset': event["endOffset"],
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
'fileName':event["fileName"],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="addselection":
event = {
"type": "addselection",
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="insert":
event = {
"type": "insert",
'index': event["index"],
'text': event["text"],
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
'fileName':event["fileName"],
'tabStatus':event['tabStatus'],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="replace":
event = {
"type": "replace",
'index': event["index"],
'length': event["length"],
'text': event["text"],
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
'fileName':event["fileName"],
'tabStatus':event['tabStatus'],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="edelelte":
event = {
"type": "edelelte",
'index': event["index"],
'length': event["length"],
'userid': event["userid"],
'userlabel': event["userlabel"],
'usercolor': event["usercolor"],
'fileName':event["fileName"],
}
websockets.broadcast(connected, json.dumps(event))
elif event["command"]["type"]=="keep-alive":
event = {
"type": "keep-alive",
}
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
async def start(websocket,events):
user = User()
connected = {websocket}
join_key = secrets.token_urlsafe(12)
JOIN[join_key] = user, 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,
}
await websocket.send(json.dumps(event))
await exe(websocket,connected,join_key)
finally:
del JOIN[join_key]
async def join(websocket, key):
"""
Handle a connection from the second player: join an existing game.
"""
# Find the Connect Four game.
try:
user, connected = JOIN[key]
except KeyError:
await error(websocket, "collabration not found.")
return
# Register to receive moves from this game.
connected.add(websocket)
try:
event = {
"type": "sendDir",
}
websockets.broadcast(connected,json.dumps(event))
await exe(websocket,connected,key)
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"])
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"])
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())