154 lines
No EOL
7 KiB
Python
154 lines
No EOL
7 KiB
Python
import os
|
|
import socket
|
|
import select
|
|
import threading
|
|
|
|
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 handle_client(sock, client_address, socket_list, usernames, chat_rooms, client_states, user_paths):
|
|
"""Handle communication with a single client."""
|
|
try:
|
|
while True:
|
|
message = sock.recv(BUFFER_SIZE).decode('utf-8')
|
|
if not message:
|
|
break
|
|
|
|
state = client_states.get(sock, "NORMAL")
|
|
if state == "WAITING_FOR_USERNAME":
|
|
usernames[sock] = message.strip()
|
|
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 == "NORMAL":
|
|
if message.startswith("@") and "-ls" in message:
|
|
# Handle directory listing request
|
|
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("@")
|
|
receiving_sock = next((s for s, u in usernames.items() if u == receiving_username), None)
|
|
if receiving_sock:
|
|
user_path = user_paths.get(receiving_sock)
|
|
if user_path and os.path.isdir(user_path):
|
|
files = os.listdir(user_path)
|
|
if files:
|
|
file_list = "\n".join(
|
|
[format_file_info(os.path.join(user_path, f)) for f in files]
|
|
)
|
|
sock.send(f"Files in {receiving_username}'s directory:\n{file_list}".encode('utf-8'))
|
|
else:
|
|
sock.send(f"No files in {receiving_username}'s directory.".encode('utf-8'))
|
|
else:
|
|
sock.send(f"Error: {receiving_username} has no valid directory set.".encode('utf-8'))
|
|
else:
|
|
sock.send(b"Error: Receiving user not found.")
|
|
else:
|
|
sock.send(b"Error: Invalid message format.")
|
|
elif message.startswith("@") and "-f" in message:
|
|
# Handle file request or transfer
|
|
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("@")
|
|
receiving_sock = next((s for s, u in usernames.items() if u == receiving_username), None)
|
|
if receiving_sock:
|
|
if len(parts) == 3:
|
|
# File request
|
|
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
|
|
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: Receiving user not found.")
|
|
else:
|
|
sock.send(b"Error: Invalid message format.")
|
|
elif message.strip() == "%!":
|
|
break
|
|
else:
|
|
username = usernames[sock]
|
|
chat_room = chat_rooms[sock]
|
|
send_message = f"{username} >> {message}".encode('utf-8')
|
|
for client_sock in socket_list:
|
|
if client_sock != sock and chat_rooms.get(client_sock) == chat_room:
|
|
client_sock.send(send_message)
|
|
except Exception as e:
|
|
print(f"Error with client {client_address}: {e}")
|
|
finally:
|
|
# Clean up on client disconnect
|
|
sock.close()
|
|
socket_list.remove(sock)
|
|
if sock in usernames:
|
|
del usernames[sock]
|
|
if sock in chat_rooms:
|
|
del chat_rooms[sock]
|
|
if sock in client_states:
|
|
del client_states[sock]
|
|
if sock in user_paths:
|
|
del user_paths[sock]
|
|
print(f"Client {client_address} disconnected.")
|
|
|
|
def main():
|
|
# Initialize server
|
|
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
server_socket.bind(('', CHATPORT))
|
|
server_socket.listen(MAX_CONNECT_QUEUE)
|
|
print(f"Chatserver listening on port: {CHATPORT}")
|
|
|
|
# Shared data structures
|
|
socket_list = []
|
|
usernames = {}
|
|
chat_rooms = {}
|
|
client_states = {}
|
|
user_paths = {}
|
|
|
|
try:
|
|
while True:
|
|
client_socket, client_address = server_socket.accept()
|
|
print(f"Accepted connection from {client_address}")
|
|
socket_list.append(client_socket)
|
|
|
|
# Initialize client state
|
|
client_states[client_socket] = "WAITING_FOR_USERNAME"
|
|
user_paths[client_socket] = None
|
|
|
|
# Start a new thread for the client
|
|
client_thread = threading.Thread(
|
|
target=handle_client,
|
|
args=(client_socket, client_address, socket_list, usernames, chat_rooms, client_states, user_paths),
|
|
daemon=True
|
|
)
|
|
client_thread.start()
|
|
except KeyboardInterrupt:
|
|
print("\nShutting down server...")
|
|
finally:
|
|
server_socket.close()
|
|
|
|
if __name__ == "__main__":
|
|
main() |