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 __init__(self, websocket,connected): super().__init__() self.websocket = websocket self.connected = connected def on_modified(self, event): if event.is_directory: return print(f'File {event.src_path} has been modified.') websockets.broadcast(self.connected,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.') websockets.broadcast(self.connected,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.') def on_moved(self, event): if event.is_directory: return print(f'File {event.src_path} has been renamed to {event.dest_path}.') 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(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) 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())