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())