From 74dca477bcba208e36e67631f37b1a3912526c74 Mon Sep 17 00:00:00 2001 From: Saba_frm Date: Mon, 29 Jun 2026 21:23:17 +0330 Subject: [PATCH] develop --- .idea/misc.xml | 4 +- .../chat/Client/ServerListener.java | 62 ++++- .../chat/Client/TransferProgress.java | 15 +- .../university/chat/Client/chatClient.java | 248 ++++++++++++++++-- .../university/chat/Common/ChatMessage.java | 6 +- .../university/chat/Common/FileMessage.java | 18 +- .../university/chat/Common/MessageType.java | 3 +- .../university/chat/Server/ChatServer.java | 44 +++- .../university/chat/Server/ClientSession.java | 158 ++++++++--- 9 files changed, 463 insertions(+), 95 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index d2b5d0f..f6a588b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,7 +8,5 @@ - - - + \ No newline at end of file diff --git a/src/main/java/com/university/chat/Client/ServerListener.java b/src/main/java/com/university/chat/Client/ServerListener.java index b609d74..79c8439 100644 --- a/src/main/java/com/university/chat/Client/ServerListener.java +++ b/src/main/java/com/university/chat/Client/ServerListener.java @@ -1,20 +1,54 @@ package com.university.chat.Client; -public class ServerListener implements Runnable{ - // TODO: store the ObjectInputStream from the user socket - // (this should be the same input stream the - // chatClient created when connecting) +import com.university.chat.Common.ChatMessage; +import com.university.chat.Common.FileMessage; + +import java.io.ObjectInputStream; + +public class ServerListener implements Runnable +{ + + private ObjectInputStream in; + + public ServerListener(ObjectInputStream in) + { + this.in = in; + } @Override - public void run() { - try { - // TODO: In an infinite loop read objects from the server - // - if it's a ChatMessage -> print ": " - // - if it's a FileMessage -> print that a file was received - // (filename + sender), it's already - // saved to disk by the server. - } catch (Exception e){ - System.out.println("Disconnected from server"); + public void run() + { + try + { + while (true) + { + Object obj = in.readObject(); + + if (obj instanceof ChatMessage msg) + { + String content = msg.getContent(); + + if (content != null && !content.trim().isEmpty()) + { + if (msg.getSender().equals("Server")) + { + System.out.println("[Server]: " + content); + } + else + { + System.out.println("[" + msg.getSender() + "]: " + content); + } + } + } + else if (obj instanceof FileMessage fileMsg) + { + System.out.println("[File received from " + fileMsg.getSender() + "]: " + fileMsg.getFilename()); + } + } + } + catch (Exception e) + { + System.out.println("Connection to server lost."); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/university/chat/Client/TransferProgress.java b/src/main/java/com/university/chat/Client/TransferProgress.java index 0e617a4..dc9e6fe 100644 --- a/src/main/java/com/university/chat/Client/TransferProgress.java +++ b/src/main/java/com/university/chat/Client/TransferProgress.java @@ -1,23 +1,27 @@ package com.university.chat.Client; -public class TransferProgress { +public class TransferProgress +{ private final long total; private final long startTime; private static final int WIDTH = 30; - public TransferProgress(long total) { + public TransferProgress(long total) + { this.total = total; this.startTime = System.currentTimeMillis(); } - public void update(long current) { + public void update(long current) + { double percent = (double) current / total; int filled = (int) (percent * WIDTH); StringBuilder bar = new StringBuilder("["); - for (int i = 0; i < WIDTH; i++) { + for (int i = 0; i < WIDTH; i++) + { bar.append(i < filled ? "█" : " "); } bar.append("]"); @@ -30,7 +34,8 @@ public class TransferProgress { (int) (percent * 100), speed); - if (current >= total) { + if (current >= total) + { System.out.println(); } } diff --git a/src/main/java/com/university/chat/Client/chatClient.java b/src/main/java/com/university/chat/Client/chatClient.java index dae0bad..53c6e40 100644 --- a/src/main/java/com/university/chat/Client/chatClient.java +++ b/src/main/java/com/university/chat/Client/chatClient.java @@ -1,29 +1,231 @@ package com.university.chat.Client; -public class chatClient { - public static void main() { - // TODO: Connecting to the server - // 1. Create a socket and connect to the server - // 2. Create an ObjectOutputStream (out) and ObjectInputStream (in) - // from the socket's streams — output FIRST, then input. - // 2. Get the username, and send a LOGIN ChatMessage with that username - // 3. Start a new Thread running a ServerListener(in) so incoming - // messages are handled concurrently. +import com.university.chat.Common.ChatMessage; +import com.university.chat.Common.FileMessage; +import com.university.chat.Common.MessageType; - while (true){ - try { - // TODO: Program loop — read a line from the console and act on it: - // - "/msg " -> build & send a PRIVATE_MESSAGE - // - "/users" -> build & send a USER_LIST request - // - "/sendfile " -> read the file into a byte[] - // (you can use TransferProgress - // to show progress) - // and send it as a FileMessage - // - anything else -> send a PUBLIC_MESSAGE - // Remember to flush() the output stream after writeObject(). - } catch (Exception e){ - System.out.println("command failed: " + e.getMessage()); +import java.io.File; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.Socket; +import java.util.Scanner; + +public class chatClient +{ + + private static final String SERVER_HOST = "localhost"; + private static final int SERVER_PORT = 5000; + + public static void main(String[] args) + { + + try + { + Socket socket = new Socket(SERVER_HOST, SERVER_PORT); + System.out.println("Connected to server."); + + + ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); + out.flush(); + ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); + + System.out.print("Enter username: "); + Scanner scanner = new Scanner(System.in); + String username = scanner.nextLine(); + + out.writeObject(new ChatMessage(MessageType.LOGIN, username, "", "")); + out.flush(); + + Thread listenerThread = new Thread(new ServerListener(in)); + listenerThread.start(); + + System.out.println("You can now chat."); + System.out.println("Commands:"); + System.out.println("/msg "); + System.out.println("/users"); + System.out.println("/sendfile "); + System.out.println("Anything else will be sent as a public message."); + System.out.print("> "); + + while (true) + { + try + { + String line = scanner.nextLine(); + + if (line == null || line.trim().isEmpty()) + { + System.out.print("> "); + continue; + } + + line = line.trim(); + + if (line.startsWith("/msg ")) + { + handlePrivateMessage(line, username, out); + } + else if (line.equals("/users")) + { + handleUserListRequest(username, out); + } + else if (line.startsWith("/sendfile ")) + { + handleFileSend(line, username, out); + } + else + { + handlePublicMessage(line, username, out); + } + + System.out.print("> "); + + } + catch (Exception e) + { + System.out.println("command failed: " + e.getMessage()); + System.out.print("> "); + } } + + } + catch (Exception e) + { + System.out.println("Could not connect to server: " + e.getMessage()); } } -} + + private static void handlePrivateMessage(String line, String username, ObjectOutputStream out) throws Exception { + + String[] parts = line.split(" ", 3); + + if (parts.length < 3) + { + System.out.println("Usage: /msg "); + return; + } + + String receiver = parts[1]; + String messageText = parts[2]; + + ChatMessage privateMessage = new ChatMessage( + MessageType.PRIVATE_MESSAGE, + username, + messageText, + receiver + ); + + out.writeObject(privateMessage); + out.flush(); + } + + private static void handleUserListRequest(String username, ObjectOutputStream out) throws Exception + { + ChatMessage userListRequest = new ChatMessage( + MessageType.USER_LIST, + username, + null, + "" + ); + + out.writeObject(userListRequest); + out.flush(); + } + + private static void handlePublicMessage(String line, String username, ObjectOutputStream out) throws Exception + { + ChatMessage publicMessage = new ChatMessage( + MessageType.PUBLIC_MESSAGE, + username, + null, + line + ); + + out.writeObject(publicMessage); + out.flush(); + } + + private static void handleFileSend(String line, String username, ObjectOutputStream out) throws Exception + { + + String[] parts = line.split(" ", 3); + + if (parts.length < 3) + { + System.out.println("Usage: /sendfile "); + return; + } + + String receiver = parts[1]; + String filePath = parts[2]; + + File file = new File(filePath); + + if (!file.exists()) + { + System.out.println("File does not exist: " + filePath); + return; + } + + if (!file.isFile()) + { + System.out.println("This path is not a file: " + filePath); + return; + } + + long totalSize = file.length(); + + TransferProgress progress = new TransferProgress(totalSize); + + byte[] data = new byte[(int) totalSize]; + + FileInputStream fis = new FileInputStream(file); + + byte[] buffer = new byte[4096]; + int bytesRead; + int totalRead = 0; + + while ((bytesRead = fis.read(buffer)) != -1) + { + + System.arraycopy(buffer, 0, data, totalRead, bytesRead); + totalRead += bytesRead; + + progress.update(totalRead); + } + + fis.close(); + + FileMessage fileMessage = new FileMessage( + username, + receiver, + file.getName(), + data + ); + + out.writeObject(fileMessage); + out.flush(); + + System.out.println("File sent successfully: " + file.getName()); + } + + private static byte[] readFileToByteArray(File file) throws Exception + { + byte[] data = new byte[(int) file.length()]; + + FileInputStream fis = new FileInputStream(file); + + int totalRead = 0; + int bytesRead; + + while (totalRead < data.length && (bytesRead = fis.read(data, totalRead, data.length - totalRead)) != -1) + { + totalRead += bytesRead; + } + + fis.close(); + + return data; + } +} \ No newline at end of file diff --git a/src/main/java/com/university/chat/Common/ChatMessage.java b/src/main/java/com/university/chat/Common/ChatMessage.java index c097745..cbc6ba1 100644 --- a/src/main/java/com/university/chat/Common/ChatMessage.java +++ b/src/main/java/com/university/chat/Common/ChatMessage.java @@ -2,7 +2,8 @@ package com.university.chat.Common; import java.io.Serializable; -public class ChatMessage implements Serializable { +public class ChatMessage implements Serializable +{ private static final long serialVersionUID = 1L; @@ -11,7 +12,8 @@ public class ChatMessage implements Serializable { private String receiver; private String content; - public ChatMessage(MessageType type, String sender, String receiver, String content) { + public ChatMessage(MessageType type, String sender, String receiver, String content) + { this.type = type; this.sender = sender; this.receiver = receiver; diff --git a/src/main/java/com/university/chat/Common/FileMessage.java b/src/main/java/com/university/chat/Common/FileMessage.java index 4f90fbf..cbd417a 100644 --- a/src/main/java/com/university/chat/Common/FileMessage.java +++ b/src/main/java/com/university/chat/Common/FileMessage.java @@ -2,7 +2,8 @@ package com.university.chat.Common; import java.io.Serializable; -public class FileMessage implements Serializable { +public class FileMessage implements Serializable +{ private static final long SerialVersionUID = 1L; @@ -11,26 +12,31 @@ public class FileMessage implements Serializable { private String filename; private byte[] data; - public FileMessage(String sender, String receiver, String filename, byte[] data) { + public FileMessage(String sender, String receiver, String filename, byte[] data) + { this.sender = sender; this.receiver = receiver; this.filename = filename; this.data = data; } - public String getSender() { + public String getSender() + { return sender; } - public String getReceiver() { + public String getReceiver() + { return receiver; } - public String getFilename() { + public String getFilename() + { return filename; } - public byte[] getData() { + public byte[] getData() + { return data; } } diff --git a/src/main/java/com/university/chat/Common/MessageType.java b/src/main/java/com/university/chat/Common/MessageType.java index 093ff8d..089a227 100644 --- a/src/main/java/com/university/chat/Common/MessageType.java +++ b/src/main/java/com/university/chat/Common/MessageType.java @@ -2,7 +2,8 @@ package com.university.chat.Common; import java.io.Serializable; -public enum MessageType implements Serializable { +public enum MessageType implements Serializable +{ LOGIN, LOGIN_SUCCESS, LOGIN_FAILED, diff --git a/src/main/java/com/university/chat/Server/ChatServer.java b/src/main/java/com/university/chat/Server/ChatServer.java index b2a35ae..99b909a 100644 --- a/src/main/java/com/university/chat/Server/ChatServer.java +++ b/src/main/java/com/university/chat/Server/ChatServer.java @@ -1,15 +1,53 @@ package com.university.chat.Server; -public class ChatServer { +import java.net.ServerSocket; +import java.net.Socket; + +public class ChatServer +{ + private static final UserManager userManager = new UserManager(); + + private static final int PORT = 5000; // TODO: declare a single shared UserManager instance (static final) // This MUST be shared by all ClientSession threads so that // broadcasting and private messaging work correctly. - public static void main(String[] args) { + public static void main(String[] args) + { + + System.out.println("Chat server starting on port " + PORT + "..."); + + try (ServerSocket serverSocket = new ServerSocket(PORT)) + { + + System.out.println("Server started. Waiting for clients..."); + + while (true) + { + + Socket clientSocket = serverSocket.accept(); + + System.out.println("New client connected: " + + clientSocket.getInetAddress()); + + ClientSession session = + new ClientSession(clientSocket, userManager); + + Thread thread = new Thread(session); + thread.start(); + } + + } + catch (Exception e) + { + System.out.println("Server error: " + e.getMessage()); + e.printStackTrace(); + } + } // TODO: Create a ServerSocket // TODO: In an infinite loop: // accept an incoming client connection // make a new thread running ClientSession for each user. - } + } diff --git a/src/main/java/com/university/chat/Server/ClientSession.java b/src/main/java/com/university/chat/Server/ClientSession.java index f09d64a..1cf3666 100644 --- a/src/main/java/com/university/chat/Server/ClientSession.java +++ b/src/main/java/com/university/chat/Server/ClientSession.java @@ -2,70 +2,152 @@ package com.university.chat.Server; import com.university.chat.Common.ChatMessage; import com.university.chat.Common.FileMessage; +import com.university.chat.Common.MessageType; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.IOException; import java.net.Socket; import java.nio.file.Files; -public class ClientSession implements Runnable { +public class ClientSession implements Runnable +{ + private final Socket socket; + private final UserManager userManager; + private ObjectOutputStream out; + private ObjectInputStream in; private String username; - public ClientSession(Socket socket, UserManager userManager) { - // TODO : Create an ObjectOutputStream from socket.getOutputStream() - // and an ObjectInputStream from socket.getInputStream(). + public ClientSession(Socket socket, UserManager userManager) + { + this.socket = socket; + this.userManager = userManager; } @Override - public void run() { - try { + public void run() + { + try + { + this.out = new ObjectOutputStream(socket.getOutputStream()); + this.out.flush(); + this.in = new ObjectInputStream(socket.getInputStream()); - // TODO: Welcome the user (login step) - // 1. Read the first object sent by the client. - // 2. Check it's a ChatMessage with type LOGIN. - // 3. Extract the username. - // 4. Try to register the user via userManager.addUser(...). - // 5. If the username is taken, send back LOGIN_FAILED and close the socket. - // 6. Otherwise, create the user's folders with FileManager.createUserFolders(...) - // and send back LOGIN_SUCCESS. + Object obj = in.readObject(); - // TODO: Main message loop - // In a loop, call in.readObject(), you can separate messages by their type: - // - if it's a ChatMessage -> call handleChatMessage(msg) - // - if it's a FileMessage -> call handleFileMessage(fileMsg) - // Keep looping until the connection is closed (an exception will be thrown). + if (obj instanceof ChatMessage loginMsg && loginMsg.getType() == MessageType.LOGIN) + { + String requestedUsername = loginMsg.getSender(); + + if (userManager.addUser(requestedUsername, this)) + { + this.username = requestedUsername; + FileManager.createUserFolders(this.username); + + send(new ChatMessage(MessageType.LOGIN_SUCCESS, "Server", "Welcome to the chat system!", "")); + } + else + { + send(new ChatMessage(MessageType.LOGIN_FAILED, "Server", "Username already taken.", "")); + socket.close(); + return; + } + } + else + { + socket.close(); + return; + } + + while (true) + { + Object receivedObj = in.readObject(); + + if (receivedObj instanceof ChatMessage msg) + { + handleChatMessage(msg); + } + else if (receivedObj instanceof FileMessage fileMsg) + { + handleFileMessage(fileMsg); + } + } - } catch (Exception e) { - System.out.println("Disconnected: " + username); - } finally { - // TODO: Remove the user from UserManager so they no longer - // receive broadcasts or appear in users list } - } - - - private void handleChatMessage(ChatMessage msg) throws IOException { - switch (msg.getType()) { - case PUBLIC_MESSAGE -> { - // TODO: Broadcast this message to every connected client. + catch (Exception e) + { + System.out.println("Disconnected: " + (username != null ? username : "Unknown client")); + } + finally + { + if (username != null) + { + userManager.removeUser(username); } - case PRIVATE_MESSAGE -> { - // TODO: Forward this message to the receiver user. + try + { + socket.close(); } - case USER_LIST -> { - // TODO: Reply to the requester with the list of online users. + catch (IOException e) + { + System.err.println("Error closing socket: " + e.getMessage()); } } } - private void handleFileMessage(FileMessage fileMsg) throws IOException { - // Storing the file + public void send(Object msg) throws IOException + { + if (out != null) + { + out.writeObject(msg); + out.flush(); + } + } + + private void handleChatMessage(ChatMessage msg) throws IOException + { + switch (msg.getType()) + { + case PUBLIC_MESSAGE -> + { + for (ClientSession session : userManager.getAllSessions()) + { + session.send(msg); + } + } + case PRIVATE_MESSAGE -> + { + ClientSession receiver = userManager.getUser(msg.getReceiver()); + if (receiver != null) + { + receiver.send(msg); + } + else + { + send(new ChatMessage(MessageType.PUBLIC_MESSAGE, "Server", "User not found: " + msg.getReceiver(), username)); + } + } + case USER_LIST -> + { + String userListStr = userManager.listUsers(); + send(new ChatMessage(MessageType.USER_LIST, "Server", userListStr, username)); + } + } + } + + private void handleFileMessage(FileMessage fileMsg) throws IOException + { var sentPath = FileManager.getSentPath(fileMsg.getSender(), fileMsg.getFilename()); var recvPath = FileManager.getReceivedPath(fileMsg.getReceiver(), fileMsg.getFilename()); Files.write(sentPath, fileMsg.getData()); Files.write(recvPath, fileMsg.getData()); - // TODO: Forward the received file-message to the destination user. + ClientSession receiver = userManager.getUser(fileMsg.getReceiver()); + if (receiver != null) + { + receiver.send(fileMsg); + } } } \ No newline at end of file