diff --git a/src/main/java/com/university/chat/Client/ServerListener.java b/src/main/java/com/university/chat/Client/ServerListener.java index b609d74..cbc8085 100644 --- a/src/main/java/com/university/chat/Client/ServerListener.java +++ b/src/main/java/com/university/chat/Client/ServerListener.java @@ -1,10 +1,16 @@ package com.university.chat.Client; +import com.university.chat.Common.ChatMessage; +import com.university.chat.Common.FileMessage; + +import java.io.ObjectInputStream; + 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) - + private ObjectInputStream in; + public ServerListener(ObjectInputStream objectInputStream){this.in = objectInputStream;} @Override public void run() { try { @@ -13,6 +19,21 @@ public class ServerListener implements Runnable{ // - if it's a FileMessage -> print that a file was received // (filename + sender), it's already // saved to disk by the server. + while (true) + { + Object obj = in.readObject(); + if(obj instanceof ChatMessage msg) + { + System.out.println("<" + msg.getSender() + ">: " + msg.getContent()); + } + else + { + FileMessage fileMsg = (FileMessage) obj; + System.out.println("a file was received"); + System.out.println("<" + fileMsg.getSender() + ">: " + fileMsg.getFilename()); + System.out.println("it's already saved to disk by the server."); + } + } } catch (Exception e){ System.out.println("Disconnected from server"); } diff --git a/src/main/java/com/university/chat/Client/chatClient.java b/src/main/java/com/university/chat/Client/chatClient.java index dae0bad..1f070cb 100644 --- a/src/main/java/com/university/chat/Client/chatClient.java +++ b/src/main/java/com/university/chat/Client/chatClient.java @@ -1,7 +1,19 @@ package com.university.chat.Client; +import com.university.chat.Common.ChatMessage; +import com.university.chat.Common.FileMessage; +import com.university.chat.Common.MessageType; +import com.university.chat.Server.FileManager; + +import javax.naming.ldap.SortKey; +import java.awt.*; +import java.io.*; +import java.net.Socket; +import java.nio.file.Files; +import java.util.Scanner; + public class chatClient { - public static void main() { + public static void main(String[] args) throws IOException { // TODO: Connecting to the server // 1. Create a socket and connect to the server // 2. Create an ObjectOutputStream (out) and ObjectInputStream (in) @@ -9,9 +21,22 @@ public class chatClient { // 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. - + Socket socket = new Socket("localhost", 12345); + ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); + out.flush(); + ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); + System.out.print("Enter your username: "); + Scanner in2 = new Scanner(System.in); + String username = in2.nextLine(); + ChatMessage logIn = new ChatMessage(MessageType.LOGIN, username, "server", "logIn"); + out.writeObject(logIn); + out.flush(); + ServerListener serverListener = new ServerListener(in); + Thread thread = new Thread(serverListener); + thread.start(); while (true){ - try { + 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 @@ -21,6 +46,47 @@ public class chatClient { // and send it as a FileMessage // - anything else -> send a PUBLIC_MESSAGE // Remember to flush() the output stream after writeObject(). + String line = in2.nextLine(); + ChatMessage msg = null; + if(line.equalsIgnoreCase("/users")) + { + msg = new ChatMessage(MessageType.USER_LIST, username, "server", "user list"); + out.writeObject(msg); + out.flush(); + } + else if(line.startsWith("/msg ")) + { + String[] parts = line.split(" ", 3); + String receiver = parts[1]; + String messageText = parts[2]; + msg = new ChatMessage(MessageType.PRIVATE_MESSAGE, username, receiver, messageText); + out.writeObject(msg); + out.flush(); + } + else if(line.startsWith("/sendfile ")) + { + String[] parts = line.split(" ", 3); + String receiver = parts[1]; + String path = parts[2]; + File file = new File(path); + byte[] fileData = Files.readAllBytes(file.toPath()); + long totalBytes = fileData.length; + TransferProgress transferProgress = new TransferProgress(totalBytes); + transferProgress.update(0); + String fileName = file.getName(); + FileMessage fileMsg = new FileMessage(username, receiver, fileName, fileData); + transferProgress.update(totalBytes/2); + out.writeObject(fileMsg); + out.flush(); + transferProgress.update(totalBytes); + } + else + { + msg = new ChatMessage(MessageType.PUBLIC_MESSAGE, username, "all", line); + out.writeObject(msg); + out.flush(); + } + } catch (Exception e){ System.out.println("command failed: " + e.getMessage()); } diff --git a/src/main/java/com/university/chat/Server/ChatServer.java b/src/main/java/com/university/chat/Server/ChatServer.java index b2a35ae..f21afe9 100644 --- a/src/main/java/com/university/chat/Server/ChatServer.java +++ b/src/main/java/com/university/chat/Server/ChatServer.java @@ -1,15 +1,36 @@ package com.university.chat.Server; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + public class ChatServer { // 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. + private static final UserManager userManager = new UserManager(); + private static final int port = 12345; + private static final ExecutorService threadPool = Executors.newCachedThreadPool(); - public static void main(String[] args) { + public static void main(String[] args) { // TODO: Create a ServerSocket - + ServerSocket serverSocket = null; + try { + serverSocket = new ServerSocket(port); + while (true) + { + Socket clientSocket = serverSocket.accept(); + ClientSession clientSession = new ClientSession(clientSocket, userManager); + threadPool.execute(clientSession); + } + } catch (IOException e) { + System.err.println("Server error: " + e.getMessage()); + } // 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..b217d9f 100644 --- a/src/main/java/com/university/chat/Server/ClientSession.java +++ b/src/main/java/com/university/chat/Server/ClientSession.java @@ -2,28 +2,66 @@ 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.IOException; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.net.Socket; import java.nio.file.Files; +import java.util.Objects; public class ClientSession implements Runnable { private String username; + private final Socket socket; + private final UserManager userManager; + private final ObjectOutputStream out; + private final ObjectInputStream in; - public ClientSession(Socket socket, UserManager userManager) { + public ClientSession(Socket socket, UserManager userManager) throws IOException { // TODO : Create an ObjectOutputStream from socket.getOutputStream() // and an ObjectInputStream from socket.getInputStream(). + this.socket = socket; + this.userManager = userManager; + this.out = new ObjectOutputStream(socket.getOutputStream()); + this.out.flush(); + this.in = new ObjectInputStream(socket.getInputStream()); } @Override public void run() { - try { - + try + { // 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. + Object obj = in.readObject(); + + if(obj instanceof ChatMessage) + { + ChatMessage msg = (ChatMessage) obj; + if(msg.getType() == MessageType.LOGIN) + { + username = msg.getSender(); + if(userManager.addUser(username, this)) + { + FileManager.createUserFolders(username); + out.writeObject(new ChatMessage(MessageType.LOGIN_SUCCESS, "server", username, "LOGIN_SUCCESS")); + System.out.println("Welcome " + username); + out.flush(); + } + else + { + out.writeObject(new ChatMessage(MessageType.LOGIN_FAILED, "server", username, "LOGIN_FAILED")); + out.flush(); + socket.close(); + return; + } + } + } // 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(...) @@ -34,12 +72,25 @@ public class ClientSession implements Runnable { // - 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). - + while (true) + { + Object obj2 = in.readObject(); + if(obj2 instanceof ChatMessage msg) + { + handleChatMessage(msg); + } + else if(obj2 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 + userManager.removeUser(username); + try { socket.close(); } catch (IOException ignored) {} } } @@ -48,13 +99,36 @@ public class ClientSession implements Runnable { switch (msg.getType()) { case PUBLIC_MESSAGE -> { // TODO: Broadcast this message to every connected client. + for (ClientSession client: userManager.getAllSessions()) + { + if(!Objects.equals(client.username, username)) + { + client.out.writeObject(msg); + client.out.flush(); + } + } + break; } case PRIVATE_MESSAGE -> { // TODO: Forward this message to the receiver user. + ClientSession receiverSession = userManager.getUser(msg.getReceiver()); + if (receiverSession != null) { + receiverSession.out.writeObject(msg); + receiverSession.out.flush(); + } + break; } case USER_LIST -> { // TODO: Reply to the requester with the list of online users. + ClientSession senderSession = userManager.getUser(msg.getSender()); + if (senderSession != null) { + ChatMessage listReply = new ChatMessage(MessageType.USER_LIST, "server", username, userManager.listUsers()); + senderSession.out.writeObject(listReply); + senderSession.out.flush(); + } + break; } + } } @@ -67,5 +141,11 @@ public class ClientSession implements Runnable { Files.write(recvPath, fileMsg.getData()); // TODO: Forward the received file-message to the destination user. + ClientSession receiverSession = userManager.getUser(fileMsg.getReceiver()); + if (receiverSession != null) + { + receiverSession.out.writeObject(fileMsg); + receiverSession.out.flush(); + } } } \ No newline at end of file