diff --git a/chatserver/client.py b/chatserver/client.py index 9ad3c98..00e4254 100644 --- a/chatserver/client.py +++ b/chatserver/client.py @@ -1,3 +1,4 @@ +import os import socket import sys import select @@ -15,13 +16,25 @@ def main(): SERVER_HOST = sys.argv[1] SERVER_PORT = int(sys.argv[2]) + # Prompt user for a local directory path for file operations + while True: + local_path = input("Enter a local directory path for sending/receiving files: ").strip() + if os.path.isdir(local_path): + print(f"Using directory: {local_path}") + break + else: + print("Invalid directory. Please enter a valid path.") + # create a socket object client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: # connect to the server client_socket.connect((SERVER_HOST, SERVER_PORT)) print(f"Connected to chat server at {SERVER_HOST}:{SERVER_PORT}") - + + # Send the local path to the server + client_socket.send(f"SET_PATH {local_path}".encode('utf-8')) + while True: # use select to wait for either server messages or user input read_sockets, _, _ = select.select([client_socket, sys.stdin], [], []) @@ -33,8 +46,35 @@ def main(): if not server_message: print("Disconnected from server") return - print(f"\n{server_message}") - print("> ", end="", flush=True) + if server_message.startswith("FILE_REQUEST"): + # Handle file request + _, sending_username, filename = server_message.split() + file_path = os.path.join(local_path, filename) + if os.path.isfile(file_path): + file_size = os.path.getsize(file_path) + with open(file_path, "rb") as f: + file_bytes = f.read() + filetype = "File" + client_socket.send( + f"@{sending_username}@CLIENT -f {filename} {filetype} {file_size} {file_bytes.decode('latin1')} ENDFILE".encode('utf-8') + ) + else: + client_socket.send( + f"@{sending_username}@CLIENT -f {filename} NOTFOUND".encode('utf-8') + ) + elif server_message.startswith("FILE_TRANSFER"): + # Handle file transfer + _, sending_username, filename, filetype, filelength = server_message.split() + file_bytes = client_socket.recv(int(filelength)) + end_marker = client_socket.recv(BUFFER_SIZE).decode('utf-8') + if end_marker == "ENDFILE": + file_path = os.path.join(local_path, filename) + with open(file_path, "wb") as f: + f.write(file_bytes) + print(f"Received file '{filename}' from {sending_username}.") + else: + print(f"\n{server_message}") + print("> ", end="", flush=True) else: # prompt for user input client_message = input() diff --git a/chatserver/server.py b/chatserver/server.py index ee1d18b..59ffe71 100644 --- a/chatserver/server.py +++ b/chatserver/server.py @@ -1,3 +1,4 @@ +import os import socket import select @@ -5,6 +6,18 @@ CHATPORT = 8181 BUFFER_SIZE = 1024 MAX_CONNECT_QUEUE = 10 +def format_file_info(file_path): + """Helper function to format file information.""" + file_size = os.path.getsize(file_path) + if file_size < 1000: + size_str = f"{file_size} B" + elif file_size < 1000000: + size_str = f"{file_size / 1000:.2f} KB" + else: + size_str = f"{file_size / 1000000:.2f} MB" + file_type = "Directory" if os.path.isdir(file_path) else "File" + return f"{os.path.basename(file_path)} - {file_type} - {size_str}" + def main(): # set the flag variable to true for infinite loop quitFlag = True @@ -31,6 +44,8 @@ def main(): # keep track of the states of each client connected, and if they are # technically in an 'unsafe' state like in change username or chatroom mode. client_states = {} + # Add a dictionary to store user-specific paths + user_paths = {} # let the user know the server is listening on port CHATPORT. print(f"Chatserver listening on port: {CHATPORT}") @@ -50,6 +65,7 @@ def main(): client_socket.send(b"\nUse '%!' to exit, or '%?' for help.\n") client_socket.send(b"\nType in a username: ") client_states[client_socket] = "WAITING_FOR_USERNAME" + user_paths[client_socket] = None # Initialize path for new client # otherwise the socket is a client socket. else: # try to receive the message from the client @@ -62,6 +78,7 @@ def main(): chat_rooms[sock] = "default" sock.send(b"\nUsername accepted. You are now in the 'default' chatroom.") client_states[sock] = "NORMAL" + user_paths[sock] = None # Initialize path for the user elif state == "WAITING_FOR_NEW_USERNAME": usernames[sock] = message.strip() sock.send(b"\nUsername changed successfully.") @@ -75,8 +92,82 @@ def main(): if old_chatroom != "default" and old_chatroom not in chat_rooms.values(): print(f"\nChatroom '{old_chatroom}' is empty and removed.") client_states[sock] = "NORMAL" - else: - if message.strip() == "%!": + elif state == "NORMAL": + if message.startswith("@") and "-ls" in message: + # Parse the message format: @RECEIVING_USERNAME@SENDING_USERNAME -ls + parts = message.split() + if len(parts) >= 2 and "-ls" in parts[1]: + usernames_part = parts[0] + if usernames_part.count("@") == 2: + receiving_username, sending_username = usernames_part[1:].split("@") + # Find the receiving user's socket + receiving_sock = next( + (s for s, u in usernames.items() if u == receiving_username), None + ) + sending_sock = next( + (s for s, u in usernames.items() if u == sending_username), None + ) + if receiving_sock and sending_sock: + user_path = user_paths.get(receiving_sock) + if user_path and os.path.isdir(user_path): + # List files in the receiving user's directory + files = os.listdir(user_path) + if files: + file_list = "\n".join( + [format_file_info(os.path.join(user_path, f)) for f in files] + ) + sending_sock.send( + f"Files in {receiving_username}'s directory:\n{file_list}".encode('utf-8') + ) + else: + sending_sock.send( + f"No files in {receiving_username}'s directory.".encode('utf-8') + ) + else: + sending_sock.send( + f"Error: {receiving_username} has no valid directory set.".encode('utf-8') + ) + else: + sock.send(b"Error: One or both users not found.") + else: + sock.send(b"Error: Invalid message format.") + elif message.startswith("@") and "-f" in message: + # Parse the message format: @RECEIVING_USERNAME@SENDING_USERNAME -f FILENAME + parts = message.split() + if len(parts) >= 3 and "-f" in parts[1]: + usernames_part = parts[0] + if usernames_part.count("@") == 2: + receiving_username, sending_username = usernames_part[1:].split("@") + # Find the receiving user's socket + receiving_sock = next( + (s for s, u in usernames.items() if u == receiving_username), None + ) + sending_sock = next( + (s for s, u in usernames.items() if u == sending_username), None + ) + if receiving_sock and sending_sock: + if len(parts) == 3: + # File request: @RECEIVING_USERNAME@SENDING_USERNAME -f FILENAME + filename = parts[2] + receiving_sock.send( + f"FILE_REQUEST {sending_username} {filename}".encode('utf-8') + ) + elif len(parts) > 5 and parts[-1] == "ENDFILE": + # File transfer: @RECEIVING_USERNAME@SENDING_USERNAME -f FILENAME FILETYPE FILELENGTH FILEBYTES ENDFILE + filename = parts[2] + filetype = parts[3] + filelength = int(parts[4]) + filebytes = " ".join(parts[5:-1]).encode('utf-8') + receiving_sock.send( + f"FILE_TRANSFER {sending_username} {filename} {filetype} {filelength}".encode('utf-8') + ) + receiving_sock.send(filebytes) + receiving_sock.send(b"ENDFILE") + else: + sock.send(b"Error: One or both users not found.") + else: + sock.send(b"Error: Invalid message format.") + elif message.strip() == "%!": sock.close() socket_list.remove(sock) del usernames[sock]