Compare commits

..

18 Commits

Author SHA1 Message Date
3115d397a4 update Sudoku submodule 2025-03-03 22:02:18 +01:00
a5a41f573b remove unused imports 2025-03-03 22:01:52 +01:00
8f30f139cd better command interface 2025-03-03 21:29:54 +01:00
Clément
c83e39ea4b preventing regex injection 2025-03-03 12:23:44 +01:00
Clément
5befdd3080 Added tag feature 2025-03-03 12:15:17 +01:00
Clément
4b8adef72f fix to prevent server chat from displaying the handshakes spam 2025-03-02 13:07:09 +01:00
Clément
39a2afcd6e fix for message where there's no room available while typing /room 2025-03-02 12:42:44 +01:00
Clément
c9e564370e Fixed bug allowing client to be in two different rooms at the same time 2025-03-02 12:37:25 +01:00
Clément
e52066ce17 Added /room command to know in which room the user is 2025-03-02 12:28:53 +01:00
Clément
e9f1feaaad Refactor to help further graphics 2025-03-02 11:53:59 +01:00
Clément
7adb581e33 fixed bug when joining room while being in another 2025-03-01 19:56:02 +01:00
Clément
10f6b059b1 escape strings for real tui 2025-03-01 19:50:19 +01:00
Clément
f8f740f799 add command aliases 2025-03-01 14:55:59 +01:00
Clément
a2c4319182 handshaking 2025-03-01 14:52:05 +01:00
90f92281ef leave/join room messages 2025-03-01 13:26:09 +01:00
0533c16cf2 refactor server room operations 2025-03-01 13:06:55 +01:00
63ec7b3aaa add disconnect 2025-03-01 13:00:58 +01:00
07ad2ba05e ClientListener + ClientConsole 2025-03-01 12:41:40 +01:00
18 changed files with 556 additions and 229 deletions

View File

@@ -29,6 +29,8 @@ You will also be able to create a new room.
- /listRooms - /listRooms
- /joinRoom *roomName* - /joinRoom *roomName*
- /leaveRoom - /leaveRoom
- /room
- /bye
- /help - /help
> [!NOTE] > [!NOTE]

View File

@@ -1,24 +1,21 @@
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Scanner;
import client.Client; import client.ClientConsole;
import server.Server; import server.Server;
public class ChatApp { public class ChatApp {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
int port = 6665; Server server = new Server(6665);
Server server = new Server(port); ClientConsole client = new ClientConsole(new InetSocketAddress("localhost", 6665));
Client client = new Client(new InetSocketAddress("localhost", port));
client.SendCreateRoom("101"); client.getClientInterface().SendCreateRoom("101");
Scanner scanner = new Scanner(System.in); client.joinThread();
while (true) {
String message = scanner.nextLine(); System.out.println("Stopping server ...");
System.out.print("\033[1A");
System.out.print("\r\033[2K"); server.close();
System.out.flush();
client.visitMessage(message); System.out.println("Done !");
}
} }
} }

View File

@@ -0,0 +1,15 @@
import client.ClientConsole;
import java.net.InetSocketAddress;
public class ChatAppClient {
public static void main(String[] args) {
ClientConsole console = new ClientConsole(new InetSocketAddress("localhost", 6665));
try {
console.joinThread();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("End !");
}
}

View File

@@ -1,142 +1,53 @@
package client; package client;
import java.io.IOException;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketException; import java.net.SocketException;
import java.util.Objects;
import java.util.Scanner;
import network.protocol.ANSIColor;
import network.protocol.packets.*; import network.protocol.packets.*;
public class Client { public class Client {
private final ClientConnexion connexion; private final ClientConnexion connexion;
private final ClientListener callback;
public static void main(String[] args) { public Client(InetSocketAddress serverAddress, ClientListener callback, String pseudo) throws SocketException {
String host = "localhost"; this.connexion = new ClientConnexion(new DatagramSocket(), serverAddress, callback);
int port = 6665; this.callback = callback;
try {
Client client = new Client(new InetSocketAddress(host, port));
Scanner scanner = new Scanner(System.in);
while(true) {
String message = scanner.nextLine();
client.visitMessage(message);
}
} catch (SocketException e) {
e.printStackTrace();
}
}
public Client(InetSocketAddress serverAddress) throws SocketException {
this.connexion = new ClientConnexion(new DatagramSocket(), serverAddress);
int tries = 0;
try {
connexion.sendPacket(new HandshakePacket());
} catch (IOException e) {
e.printStackTrace();
}
while(!connexion.connected) {
try {
Thread.sleep(100);
if(tries++ > 5) {
System.out.println("Server is not responding");
System.exit(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Scanner scanner = new Scanner(System.in);
System.out.println("Enter your pseudo:");
String pseudo = scanner.nextLine();
login(pseudo); login(pseudo);
} }
public void visitMessage(String message){ public void close() {
try { this.connexion.sendPacket(new DisconnectPacket("Leaving"));
if(message.startsWith("/")){ this.connexion.close();
if(message.startsWith("/createRoom ")) { this.callback.handleDisconnect();
String roomName = message.substring(12).trim();
SendCreateRoom(roomName);
} else if(message.startsWith("/create ")){
String roomName = message.substring(8).trim();
SendCreateRoom(roomName);
} else if(message.equals("/listRooms") || message.equals("/list")) {
RequestRoomList();
} else if(message.startsWith("/joinRoom ")) {
String roomName = message.substring(10).trim();
SendJoinRoom(roomName);
} else if(message.startsWith("/join ")){
String roomName = message.substring(6).trim();
SendJoinRoom(roomName);
} else if(message.equals("/leaveRoom") || message.equals("/leave")) {
SendLeaveRoom();
} else if(message.equals("/help")) {
System.out.println("Available commands:");
System.out.println("\t/createRoom <roomName>");
System.out.println("\t/listRooms");
System.out.println("\t/joinRoom <roomName>");
System.out.println("\t/leaveRoom");
System.out.println("\t/help");
}
else {
System.out.println(ANSIColor.formatString("&rUnknown command&n"));
}
} else {
SendChatMessage(message);
}
} catch (Exception e) {
e.printStackTrace();
}
} }
private void login(String pseudo) { private void login(String pseudo) {
try {
this.connexion.sendPacket(new LoginPacket(pseudo)); this.connexion.sendPacket(new LoginPacket(pseudo));
} catch (IOException e) {
e.printStackTrace();
}
} }
public void SendChatMessage(String message) { public void SendChatMessage(String message) {
try {
this.connexion.sendPacket(new SendChatMessagePacket(message)); this.connexion.sendPacket(new SendChatMessagePacket(message));
} catch (IOException e) {
e.printStackTrace();
}
} }
public void SendCreateRoom(String roomName) { public void SendCreateRoom(String roomName) {
try {
this.connexion.sendPacket(new CreateRoomPacket(roomName)); this.connexion.sendPacket(new CreateRoomPacket(roomName));
} catch (Exception e) {
e.printStackTrace();
}
} }
public void SendJoinRoom(String roomName) { public void SendJoinRoom(String roomName) {
try {
this.connexion.sendPacket(new JoinRoomPacket(roomName)); this.connexion.sendPacket(new JoinRoomPacket(roomName));
} catch (Exception e) {
e.printStackTrace();
}
} }
public void SendLeaveRoom() { public void SendLeaveRoom() {
try {
this.connexion.sendPacket(new LeaveRoomPacket()); this.connexion.sendPacket(new LeaveRoomPacket());
} catch (Exception e) {
e.printStackTrace();
}
} }
public void RequestRoomList() { public void RequestRoomList() {
try {
this.connexion.sendPacket(new RequestRoomListPacket()); this.connexion.sendPacket(new RequestRoomListPacket());
} catch (Exception e) {
e.printStackTrace();
} }
public void RequestActualRoom() {
this.connexion.sendPacket(new RequestActualRoomPacket());
} }
} }

View File

@@ -7,30 +7,58 @@ import java.net.InetSocketAddress;
import network.PacketHandler; import network.PacketHandler;
import network.SocketReader; import network.SocketReader;
import network.SocketWriter; import network.SocketWriter;
import network.protocol.ANSIColor;
import network.protocol.Packet; import network.protocol.Packet;
import network.protocol.PacketVisitor; import network.protocol.PacketVisitor;
import network.protocol.packets.*; import network.protocol.packets.*;
public class ClientConnexion implements PacketVisitor, PacketHandler{ public class ClientConnexion implements PacketVisitor, PacketHandler {
private final InetSocketAddress serverAddress; private final InetSocketAddress serverAddress;
private final SocketWriter writer; private final SocketWriter writer;
private final SocketReader reader; private final SocketReader reader;
protected boolean connected = false; private final ClientListener callback;
private volatile boolean connected = false;
public ClientConnexion(DatagramSocket socket, InetSocketAddress serverAddress) { public ClientConnexion(DatagramSocket socket, InetSocketAddress serverAddress, ClientListener callback) {
this.serverAddress = serverAddress; this.serverAddress = serverAddress;
this.writer = new SocketWriter(socket); this.writer = new SocketWriter(socket);
this.reader = new SocketReader(socket, this); this.reader = new SocketReader(socket, this);
this.callback = callback;
spamHandshake();
}
private void spamHandshake() {
for (int i = 0; i < 5; i++) {
sendPacket(new HandshakePacket());
}
new Thread(this::waitForHandshake).start();
}
private void waitForHandshake() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!connected) {
System.out.println("The server did not respond !");
this.close();
this.callback.handleConnexionError();
}
} }
public void close() { public void close() {
this.reader.stop(); this.reader.stop();
} }
public void sendPacket(Packet packet) throws IOException { public void sendPacket(Packet packet) {
try {
this.writer.sendPacket(packet, serverAddress); this.writer.sendPacket(packet, serverAddress);
} catch (IOException e) {
this.close();
this.callback.handleConnexionError();
e.printStackTrace();
}
} }
@Override @Override
@@ -41,16 +69,41 @@ public class ClientConnexion implements PacketVisitor, PacketHandler{
@Override @Override
public void visitPacket(ChatMessagePacket packet) { public void visitPacket(ChatMessagePacket packet) {
StringBuilder sb = new StringBuilder(); this.callback.handleChatMessage(packet.getTime(), packet.getChatter(), packet.getContent());
String time = packet.getTime().toString(); }
sb.append("&y[")
.append(time, 11, 19) // We only take the HH:MM:SS part @Override
.append("]&n") public void visitPacket(RoomListPacket packet) {
.append(" ") this.callback.handleRoomList(packet.getRoomNames());
.append(packet.getChatter()) }
.append(" : ")
.append(packet.getContent()).append("&n"); // make the color back to normal at the end of every message @Override
System.out.println(ANSIColor.formatString(sb.toString())); public void visitPacket(ServerResponsePacket packet) {
this.callback.handleServerResponse(packet.getResponse());
}
@Override
public void visitPacket(RequestActualRoomPacket packet) {
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
}
@Override
public void visitPacket(ActualRoomPacket packet) {
this.callback.handleActualRoom(packet.getRoomName());
}
@Override
public void visitPacket(DisconnectPacket packet) {
this.close();
this.connected = false;
this.callback.handleDisconnect();
}
@Override
public void visitPacket(HandshakePacket packet) {
if (!connected)
this.callback.handleConnect();
this.connected = true;
} }
@Override @Override
@@ -78,29 +131,9 @@ public class ClientConnexion implements PacketVisitor, PacketHandler{
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'"); throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
} }
@Override
public void visitPacket(RoomListPacket packet) {
System.out.println("Rooms :");
for (String room : packet.getRoomNames()) {
System.out.println("\t" + room);
}
}
@Override @Override
public void visitPacket(SendChatMessagePacket packet) { public void visitPacket(SendChatMessagePacket packet) {
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'"); throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
} }
@Override
public void visitPacket(ServerResponsePacket packet) {
if(packet.getResponse() == ServerResponsePacket.Response.MessageSent || packet.getResponse() == ServerResponsePacket.Response.MessageNotSent) {
return;
}
System.out.println(packet.getResponse());
}
@Override
public void visitPacket(HandshakePacket packet) {
connected = true;
}
} }

View File

@@ -0,0 +1,197 @@
package client;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.function.Consumer;
import network.protocol.ANSIColor;
import network.protocol.packets.ServerResponsePacket;
import network.protocol.packets.ServerResponsePacket.Response;
public class ClientConsole implements ClientListener {
private Client client;
private final Thread inputThread;
private final Scanner scanner;
private volatile boolean connected = false;
public ClientConsole(InetSocketAddress address) {
this.inputThread = new Thread(this::inputLoop);
this.scanner = new Scanner(System.in);
String pseudo = inputPseudo();
try {
this.client = new Client(address, this, pseudo);
this.inputThread.start();
} catch (SocketException e) {
e.printStackTrace();
}
}
private String inputPseudo() {
System.out.println("Enter your pseudo:");
String pseudo = "chatter";
pseudo = this.scanner.nextLine();
return pseudo;
}
public Client getClientInterface() {
return this.client;
}
private void inputLoop() {
// waiting to be connected
try {
Thread.sleep(2000);
return;
} catch (InterruptedException e) {
if (!connected)
return;
}
// resets the interrupt
Thread.interrupted();
while (!Thread.interrupted()) {
String message = this.scanner.nextLine();
if (Thread.interrupted())
break;
visitMessage(message);
}
}
public void joinThread() throws InterruptedException {
this.inputThread.join();
}
private void printHelp(Map<List<String>, Consumer<String>> commands) {
System.out.println("Available commands:");
for (var entry : commands.entrySet()) {
List<String> commandNames = entry.getKey();
System.out.println("\t" + commandNames.get(0) + (commandNames.size() == 2 ? " or " + commandNames.get(1) : ""));
}
// the clearLine eats the last line if a new line is not skipped
System.out.println("\n");
}
private void visitMessage(String message) {
if (!message.startsWith("/")) {
this.client.SendChatMessage(message);
clearLine();
return;
}
final Map<List<String>, Consumer<String>> commands = Map.of(
List.of("/createRoom <roomName>", "/create <roomName>"), (args) -> this.client.SendCreateRoom(args),
List.of("/listRooms", "/list"), (args) -> this.client.RequestRoomList(),
List.of("/joinRoom <roomName>", "/join <roomName>"), (args) -> this.client.SendJoinRoom(args),
List.of("/leaveRoom", "/leave"), (args) -> this.client.SendLeaveRoom(),
List.of("/actualRoom", "/room"), (args) -> this.client.RequestActualRoom(),
List.of("/goodbye", "/bye"), (args) -> this.client.close(),
List.of("/help"), (args) -> {});
boolean commandFound = false;
for (var entry : commands.entrySet()) {
List<String> commandNames = entry.getKey();
Consumer<String> runnable = entry.getValue();
for (String cmd : commandNames) {
String[] cmdParts = cmd.split(" ");
if (cmdParts.length > 1) {
// the command expects arguments
if (message.startsWith(cmdParts[0] + " ")) {
runnable.accept(message.substring(cmdParts[0].length() + 1).trim());
commandFound = true;
}
} else {
// the command does not expect arguments
if (message.equals(cmd)) {
runnable.accept(message.substring(cmd.length()).trim());
commandFound = true;
}
}
}
if (commandFound)
break;
}
if (commandFound) {
if (message.equals("/help"))
printHelp(commands);
clearLine();
return;
}
System.out.println(ANSIColor.formatString("&rUnknown command&n"));
printHelp(commands);
clearLine();
}
private void clearLine() {
System.out.print("\033[1A\r\033[2K"); // weird sequence to clear the line (but it works !)
System.out.flush();
}
private void stop() {
this.inputThread.interrupt();
}
@Override
public void handleDisconnect() {
System.out.println("Disconnected !");
stop();
}
@Override
public void handleConnexionError() {
System.out.println("An error occured during the connexion !");
this.connected = false;
stop();
}
@Override
public void handleChatMessage(Instant time, String chatter, String content) {
StringBuilder sb = new StringBuilder();
String strTime = time.toString();
sb.append("&y[")
.append(strTime, 11, 19) // We only take the HH:MM:SS part
.append("]&n")
.append(" ")
.append(chatter)
.append(" : ")
.append(content).append("&n"); // make the color back to normal at the end of every message
System.out.println(ANSIColor.formatString(sb.toString()));
}
@Override
public void handleRoomList(List<String> roomNames) {
System.out.println(roomNames.isEmpty()
? "No rooms available"
: "Rooms : \n\t" + String.join("\n\t", roomNames));
}
@Override
public void handleActualRoom(String roomName) {
System.out.println(roomName != null ? ANSIColor.formatString("You are now in room &b" + roomName + "&n") : "You are not in a room");
}
@Override
public void handleServerResponse(Response response) {
if (response == ServerResponsePacket.Response.MessageSent
|| response == ServerResponsePacket.Response.MessageNotSent) {
return;
}
System.out.println(response);
}
@Override
public void handleConnect() {
System.out.println("Connected to server !");
this.connected = true;
this.inputThread.interrupt();
}
}

View File

@@ -0,0 +1,18 @@
package client;
import java.time.Instant;
import java.util.List;
import network.protocol.packets.ServerResponsePacket;
public interface ClientListener {
void handleDisconnect();
void handleConnexionError();
void handleConnect();
void handleChatMessage(Instant time, String chatter, String content);
void handleRoomList(List<String> roomNames);
void handleServerResponse(ServerResponsePacket.Response response);
void handleActualRoom(String roomName);
}

View File

@@ -24,6 +24,7 @@ public class SocketReader {
public void stop() { public void stop() {
this.readThread.interrupt(); this.readThread.interrupt();
socket.close();
} }
private void readLoop() { private void readLoop() {

View File

@@ -1,5 +1,7 @@
package network.protocol; package network.protocol;
import java.util.regex.Pattern;
public class ANSIColor { public class ANSIColor {
public static final String RESET = "\u001B[0m"; public static final String RESET = "\u001B[0m";
public static final String BLACK = "\u001B[30m"; public static final String BLACK = "\u001B[30m";
@@ -15,4 +17,8 @@ public class ANSIColor {
.replace("&y", GREY) .replace("&y", GREY)
.replace("&n", RESET); .replace("&n", RESET);
} }
public static String tag(String message, String chatter){
return message.replaceAll("(@" + Pattern.quote(chatter) + ")", "\u001B[44;30m$1\u001B[49;39m" );
}
} }

View File

@@ -10,6 +10,8 @@ public interface PacketVisitor {
void visitPacket(ChatMessagePacket packet); void visitPacket(ChatMessagePacket packet);
void visitPacket(CreateRoomPacket packet); void visitPacket(CreateRoomPacket packet);
void visitPacket(DisconnectPacket packet);
void visitPacket(HandshakePacket packet);
void visitPacket(JoinRoomPacket packet); void visitPacket(JoinRoomPacket packet);
void visitPacket(LeaveRoomPacket packet); void visitPacket(LeaveRoomPacket packet);
void visitPacket(LoginPacket packet); void visitPacket(LoginPacket packet);
@@ -17,5 +19,7 @@ public interface PacketVisitor {
void visitPacket(RoomListPacket packet); void visitPacket(RoomListPacket packet);
void visitPacket(SendChatMessagePacket packet); void visitPacket(SendChatMessagePacket packet);
void visitPacket(ServerResponsePacket packet); void visitPacket(ServerResponsePacket packet);
void visitPacket(HandshakePacket packet); void visitPacket(RequestActualRoomPacket packet);
void visitPacket(ActualRoomPacket packet);
} }

View File

@@ -0,0 +1,21 @@
package network.protocol.packets;
import network.protocol.Packet;
import network.protocol.PacketVisitor;
public class ActualRoomPacket extends Packet {
private final String roomName;
public ActualRoomPacket(String roomName) {
this.roomName = roomName;
}
public String getRoomName() {
return roomName;
}
@Override
public void accept(PacketVisitor packetVisitor) {
packetVisitor.visitPacket(this);
}
}

View File

@@ -0,0 +1,23 @@
package network.protocol.packets;
import network.protocol.Packet;
import network.protocol.PacketVisitor;
public class DisconnectPacket extends Packet {
private final String reason;
public DisconnectPacket(String reason) {
this.reason = reason;
}
public String getReason() {
return reason;
}
@Override
public void accept(PacketVisitor packetVisitor) {
packetVisitor.visitPacket(this);
}
}

View File

@@ -0,0 +1,14 @@
package network.protocol.packets;
import network.protocol.Packet;
import network.protocol.PacketVisitor;
public class RequestActualRoomPacket extends Packet {
public RequestActualRoomPacket() {
}
@Override
public void accept(PacketVisitor packetVisitor) {
packetVisitor.visitPacket(this);
}
}

View File

@@ -1,19 +1,19 @@
package network.protocol.packets; package network.protocol.packets;
import java.util.ArrayList; import java.util.List;
import network.protocol.Packet; import network.protocol.Packet;
import network.protocol.PacketVisitor; import network.protocol.PacketVisitor;
public class RoomListPacket extends Packet { public class RoomListPacket extends Packet {
private final ArrayList<String> roomNames; private final List<String> roomNames;
public RoomListPacket(ArrayList<String> roomNames) { public RoomListPacket(List<String> roomNames) {
this.roomNames = roomNames; this.roomNames = roomNames;
} }
public ArrayList<String> getRoomNames() { public List<String> getRoomNames() {
return roomNames; return roomNames;
} }

View File

@@ -10,6 +10,7 @@ import java.util.Map;
import network.PacketHandler; import network.PacketHandler;
import network.SocketReader; import network.SocketReader;
import network.protocol.ANSIColor;
import network.protocol.Packet; import network.protocol.Packet;
import network.protocol.packets.ChatMessagePacket; import network.protocol.packets.ChatMessagePacket;
import network.protocol.packets.SendChatMessagePacket; import network.protocol.packets.SendChatMessagePacket;
@@ -18,66 +19,85 @@ public class Server implements PacketHandler {
private final DatagramSocket serverSocket; private final DatagramSocket serverSocket;
private final Map<InetSocketAddress, ServerConnexion> connexions; private final Map<InetSocketAddress, ServerConnexion> connexions;
private final Map<InetSocketAddress, Instant> connexionTimes;
private final SocketReader reader; private final SocketReader reader;
private final Map<String, ArrayList<ServerConnexion>> roomNames; private final Map<String, ArrayList<ServerConnexion>> rooms;
public Server(int port) throws SocketException { public Server(int port) throws SocketException {
this.serverSocket = new DatagramSocket(port); this.serverSocket = new DatagramSocket(port);
this.connexions = new HashMap<>(); this.connexions = new HashMap<>();
this.connexionTimes = new HashMap<>();
this.reader = new SocketReader(serverSocket, this); this.reader = new SocketReader(serverSocket, this);
this.roomNames = new HashMap<>(); this.rooms = new HashMap<>();
} }
public ArrayList<String> getRoomNames() { public ArrayList<String> getRoomNames() {
return roomNames.keySet().stream().collect(ArrayList::new, ArrayList::add, ArrayList::addAll); return rooms.keySet().stream().collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
}
public boolean isInRoom(ServerConnexion connexion) {
return getRoomName(connexion) != null;
} }
public String getRoomName(ServerConnexion connexion) { public String getRoomName(ServerConnexion connexion) {
for (Map.Entry<String, ArrayList<ServerConnexion>> entry : roomNames.entrySet()) { for (Map.Entry<String, ArrayList<ServerConnexion>> entry : rooms.entrySet()) {
if(entry.getValue().contains(connexion)) { if (entry.getValue().contains(connexion)) {
return entry.getKey(); return entry.getKey();
} }
} }
return null; return null;
} }
public void createRoom(String roomName, ServerConnexion connexion) throws SocketException { public boolean createRoom(String roomName, ServerConnexion connexion) {
if(roomNames.containsKey(roomName)) { if (rooms.containsKey(roomName)) {
throw new SocketException("Room already exists"); return false;
} }
roomNames.put(roomName, new ArrayList<>()); rooms.put(roomName, new ArrayList<>());
roomNames.get(roomName).add(connexion); leaveRoom(connexion); // Leave the current room (auto handle if not in a room)
rooms.get(roomName).add(connexion); // Add the creator to the room
return true;
} }
public void leaveRoom(ServerConnexion connexion) throws SocketException { public boolean leaveRoom(ServerConnexion connexion) {
String roomName = getRoomName(connexion); String roomName = getRoomName(connexion);
if(roomName != null) { if (roomName != null) {
roomNames.get(roomName).remove(connexion); rooms.get(roomName).remove(connexion);
// Remove the room if it is empty // Remove the room if it is empty
if(roomNames.get(roomName).isEmpty()) { if (rooms.get(roomName).isEmpty()) {
roomNames.remove(roomName); rooms.remove(roomName);
} }
return; return true;
} }
throw new SocketException("Room does not exist"); return false;
} }
public void joinRoom(String roomName, ServerConnexion connexion) throws SocketException { public boolean joinRoom(String roomName, ServerConnexion connexion) {
if(roomNames.containsKey(roomName) && !roomNames.get(roomName).contains(connexion)) { leaveRoom(connexion); // Leave the current room (auto handle if not in a room)
roomNames.get(roomName).add(connexion); if (rooms.containsKey(roomName) && !rooms.get(roomName).contains(connexion)) {
return; rooms.get(roomName).add(connexion);
return true;
} }
throw new SocketException("Room does not exist"); return false;
} }
public void sendToRoom(ServerConnexion connexion, SendChatMessagePacket packet) throws SocketException { public boolean sendToRoom(String roomName, ChatMessagePacket packet) {
if (roomName != null && rooms.containsKey(roomName)) {
rooms.get(roomName).forEach(con -> con.sendPacket(
new ChatMessagePacket(packet.getTime(), packet.getChatter(), ANSIColor.tag(packet.getContent(), con.getChatterName()))
));
return true;
}
return false;
}
public boolean sendToRoom(ServerConnexion connexion, ChatMessagePacket packet) {
String roomName = getRoomName(connexion); String roomName = getRoomName(connexion);
ChatMessagePacket chatPacket = new ChatMessagePacket(Instant.now(), connexion.getChatterName(), packet.getContent()); return sendToRoom(roomName, packet);
if(roomName != null && roomNames.containsKey(roomName)) {
roomNames.get(roomName).forEach(con -> con.sendPacket(chatPacket));
return;
} }
throw new SocketException("You are not in a room or the room does not exist");
public boolean sendToRoom(ServerConnexion connexion, SendChatMessagePacket packet) {
return sendToRoom(connexion,
new ChatMessagePacket(Instant.now(), connexion.getChatterName(), packet.getContent()));
} }
public void close() { public void close() {
@@ -89,6 +109,20 @@ public class Server implements PacketHandler {
.anyMatch(connexion -> pseudo.equals(connexion.getChatterName())); .anyMatch(connexion -> pseudo.equals(connexion.getChatterName()));
} }
void removeConnexion(ServerConnexion connexion) {
for (var it = this.connexions.entrySet().iterator(); it.hasNext();) {
var entry = it.next();
if (entry.getValue() == connexion) {
it.remove();
break;
}
}
for (var entry : this.rooms.entrySet()) {
if (entry.getValue().remove(connexion))
return;
}
}
@Override @Override
public void handlePacket(Packet packet, InetSocketAddress address) { public void handlePacket(Packet packet, InetSocketAddress address) {
if (!connexions.containsKey(address)) { if (!connexions.containsKey(address)) {
@@ -97,4 +131,22 @@ public class Server implements PacketHandler {
this.connexions.get(address).visit(packet); this.connexions.get(address).visit(packet);
} }
/**
* Avoid the server to spam the chat
* @param address the address of the connexion
* @return true if the connexion is the first received or older than 5 seconds
*/
public boolean handleConnexionTime(InetSocketAddress address) {
if (!connexionTimes.containsKey(address)) {
connexionTimes.put(address, Instant.now());
return true;
}
Instant lastConnexion = connexionTimes.get(address);
if (Instant.now().isAfter(lastConnexion.plusSeconds(5))) {
connexionTimes.put(address, Instant.now());
return true;
}
return false;
}
} }

View File

@@ -3,7 +3,7 @@ package server;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketException; import java.time.Instant;
import network.SocketWriter; import network.SocketWriter;
import network.protocol.Packet; import network.protocol.Packet;
@@ -33,48 +33,42 @@ public class ServerConnexion implements PacketVisitor {
this.writer.sendPacket(packet, clientAddress); this.writer.sendPacket(packet, clientAddress);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
this.server.removeConnexion(this);
} }
} }
@Override
public void visitPacket(ChatMessagePacket packet) {
// I'm never supposed to receive this from the client
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
}
@Override @Override
public void visitPacket(CreateRoomPacket packet) { public void visitPacket(CreateRoomPacket packet) {
try { boolean created = server.createRoom(packet.getRoomName(), this);
server.createRoom(packet.getRoomName(), this); sendPacket(new ServerResponsePacket(created ? Response.RoomCreated : Response.RoomNotCreated));
sendPacket(new ServerResponsePacket(Response.RoomCreated)); if (created)
} catch (SocketException e) { onRoomJoin();
sendPacket(new ServerResponsePacket(Response.RoomNotCreated));
}
} }
@Override @Override
public void visitPacket(JoinRoomPacket packet) { public void visitPacket(JoinRoomPacket packet) {
try { if(server.getRoomName(this) != null) {
server.joinRoom(packet.getRoomName(), this); server.leaveRoom(this);
sendPacket(new ServerResponsePacket(Response.RoomJoined)); return;
} catch (SocketException e) {
sendPacket(new ServerResponsePacket(Response.RoomNotJoined));
} }
boolean joined = server.joinRoom(packet.getRoomName(), this);
sendPacket(new ServerResponsePacket(joined ? Response.RoomJoined : Response.RoomNotJoined));
if (joined)
onRoomJoin();
} }
@Override @Override
public void visitPacket(LeaveRoomPacket packet) { public void visitPacket(LeaveRoomPacket packet) {
try { String roomName = this.server.getRoomName(this);
server.leaveRoom(this); boolean left = server.leaveRoom(this);
sendPacket(new ServerResponsePacket(Response.RoomLeft)); sendPacket(new ServerResponsePacket(left ? Response.RoomLeft : Response.RoomNotLeft));
} catch (SocketException e) { if (left)
sendPacket(new ServerResponsePacket(Response.RoomNotLeft)); onRoomLeave(roomName);
}
} }
@Override @Override
public void visitPacket(LoginPacket packet) { public void visitPacket(LoginPacket packet) {
if (server.hasChatterName(packet.getPseudo())) { if (packet.getPseudo().isEmpty() || server.hasChatterName(packet.getPseudo())) {
sendPacket(new ServerResponsePacket(Response.AuthError)); sendPacket(new ServerResponsePacket(Response.AuthError));
return; return;
} }
@@ -89,6 +83,43 @@ public class ServerConnexion implements PacketVisitor {
sendPacket(new RoomListPacket(server.getRoomNames())); sendPacket(new RoomListPacket(server.getRoomNames()));
} }
@Override
public void visitPacket(SendChatMessagePacket packet) {
boolean messageSent = server.sendToRoom(this, packet);
sendPacket(new ServerResponsePacket(messageSent ? Response.MessageSent : Response.MessageNotSent));
}
@Override
public void visitPacket(DisconnectPacket packet) {
this.onDisconnect();
}
@Override
public void visitPacket(HandshakePacket packet) {
if(this.server.handleConnexionTime(this.clientAddress)) {
System.out.println("[Server] Handshake received from " + clientAddress);
}
sendPacket(packet);
}
private void onDisconnect() {
if (this.server.isInRoom(this)) {
this.onRoomLeave(this.server.getRoomName(this));
}
this.server.removeConnexion(this);
System.out.println("[Server] Chatter " + chatterName + " disconnected !");
}
private void onRoomJoin() {
String joinMessage = "Chatter " + this.chatterName + " joined the room !";
this.server.sendToRoom(this, new ChatMessagePacket(Instant.now(), "", joinMessage));
}
private void onRoomLeave(String roomName) {
String joinMessage = "Chatter " + this.chatterName + " left the room !";
this.server.sendToRoom(roomName, new ChatMessagePacket(Instant.now(), "", joinMessage));
}
@Override @Override
public void visitPacket(RoomListPacket packet) { public void visitPacket(RoomListPacket packet) {
// I'm never supposed to receive this from the client // I'm never supposed to receive this from the client
@@ -96,12 +127,9 @@ public class ServerConnexion implements PacketVisitor {
} }
@Override @Override
public void visitPacket(SendChatMessagePacket packet) { public void visitPacket(ChatMessagePacket packet) {
try { // I'm never supposed to receive this from the client
server.sendToRoom(this, packet); throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
} catch (SocketException e) {
sendPacket(new ServerResponsePacket(Response.MessageNotSent));
}
} }
@Override @Override
@@ -111,9 +139,14 @@ public class ServerConnexion implements PacketVisitor {
} }
@Override @Override
public void visitPacket(HandshakePacket packet) { public void visitPacket(RequestActualRoomPacket packet) {
System.out.println("[Server] Handshake received from " + clientAddress); sendPacket(new ActualRoomPacket(server.getRoomName(this)));
sendPacket(new HandshakePacket()); }
@Override
public void visitPacket(ActualRoomPacket packet) {
// I'm never supposed to receive this from the client
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
} }
} }

2
Sudoku

Submodule Sudoku updated: af0ac0ff77...05df8a56a7