Migrate everything on Gradle
Also added tasks to run the server & client separately
This commit is contained in:
152
ChatApp/app/src/main/java/server/Server.java
Normal file
152
ChatApp/app/src/main/java/server/Server.java
Normal file
@@ -0,0 +1,152 @@
|
||||
package server;
|
||||
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import network.PacketHandler;
|
||||
import network.SocketReader;
|
||||
import network.protocol.ANSIColor;
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.packets.ChatMessagePacket;
|
||||
import network.protocol.packets.SendChatMessagePacket;
|
||||
|
||||
public class Server implements PacketHandler {
|
||||
|
||||
private final DatagramSocket serverSocket;
|
||||
private final Map<InetSocketAddress, ServerConnexion> connexions;
|
||||
private final Map<InetSocketAddress, Instant> connexionTimes;
|
||||
private final SocketReader reader;
|
||||
private final Map<String, ArrayList<ServerConnexion>> rooms;
|
||||
|
||||
public Server(int port) throws SocketException {
|
||||
this.serverSocket = new DatagramSocket(port);
|
||||
this.connexions = new HashMap<>();
|
||||
this.connexionTimes = new HashMap<>();
|
||||
this.reader = new SocketReader(serverSocket, this);
|
||||
this.rooms = new HashMap<>();
|
||||
}
|
||||
|
||||
public ArrayList<String> getRoomNames() {
|
||||
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) {
|
||||
for (Map.Entry<String, ArrayList<ServerConnexion>> entry : rooms.entrySet()) {
|
||||
if (entry.getValue().contains(connexion)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean createRoom(String roomName, ServerConnexion connexion) {
|
||||
if (rooms.containsKey(roomName)) {
|
||||
return false;
|
||||
}
|
||||
rooms.put(roomName, new ArrayList<>());
|
||||
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 boolean leaveRoom(ServerConnexion connexion) {
|
||||
String roomName = getRoomName(connexion);
|
||||
if (roomName != null) {
|
||||
rooms.get(roomName).remove(connexion);
|
||||
// Remove the room if it is empty
|
||||
if (rooms.get(roomName).isEmpty()) {
|
||||
rooms.remove(roomName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean joinRoom(String roomName, ServerConnexion connexion) {
|
||||
leaveRoom(connexion); // Leave the current room (auto handle if not in a room)
|
||||
if (rooms.containsKey(roomName) && !rooms.get(roomName).contains(connexion)) {
|
||||
rooms.get(roomName).add(connexion);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
return sendToRoom(roomName, packet);
|
||||
}
|
||||
|
||||
public boolean sendToRoom(ServerConnexion connexion, SendChatMessagePacket packet) {
|
||||
return sendToRoom(connexion,
|
||||
new ChatMessagePacket(Instant.now(), connexion.getChatterName(), packet.getContent()));
|
||||
}
|
||||
|
||||
public void close() {
|
||||
this.reader.stop();
|
||||
}
|
||||
|
||||
public boolean hasChatterName(String pseudo) {
|
||||
return this.connexions.values().stream()
|
||||
.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
|
||||
public void handlePacket(Packet packet, InetSocketAddress address) {
|
||||
if (!connexions.containsKey(address)) {
|
||||
this.connexions.put(address, new ServerConnexion(this, serverSocket, address));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
152
ChatApp/app/src/main/java/server/ServerConnexion.java
Normal file
152
ChatApp/app/src/main/java/server/ServerConnexion.java
Normal file
@@ -0,0 +1,152 @@
|
||||
package server;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Instant;
|
||||
|
||||
import network.SocketWriter;
|
||||
import network.protocol.Packet;
|
||||
import network.protocol.PacketVisitor;
|
||||
import network.protocol.packets.*;
|
||||
import network.protocol.packets.ServerResponsePacket.Response;
|
||||
|
||||
public class ServerConnexion implements PacketVisitor {
|
||||
|
||||
private final Server server;
|
||||
private final InetSocketAddress clientAddress;
|
||||
private final SocketWriter writer;
|
||||
private String chatterName;
|
||||
|
||||
public ServerConnexion(Server server, DatagramSocket socket, InetSocketAddress clientAddress) {
|
||||
this.clientAddress = clientAddress;
|
||||
this.server = server;
|
||||
this.writer = new SocketWriter(socket);
|
||||
}
|
||||
|
||||
public String getChatterName() {
|
||||
return this.chatterName;
|
||||
}
|
||||
|
||||
public void sendPacket(Packet packet) {
|
||||
try {
|
||||
this.writer.sendPacket(packet, clientAddress);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
this.server.removeConnexion(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(CreateRoomPacket packet) {
|
||||
boolean created = server.createRoom(packet.getRoomName(), this);
|
||||
sendPacket(new ServerResponsePacket(created ? Response.RoomCreated : Response.RoomNotCreated));
|
||||
if (created)
|
||||
onRoomJoin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(JoinRoomPacket packet) {
|
||||
if(server.getRoomName(this) != null) {
|
||||
server.leaveRoom(this);
|
||||
return;
|
||||
}
|
||||
boolean joined = server.joinRoom(packet.getRoomName(), this);
|
||||
sendPacket(new ServerResponsePacket(joined ? Response.RoomJoined : Response.RoomNotJoined));
|
||||
if (joined)
|
||||
onRoomJoin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(LeaveRoomPacket packet) {
|
||||
String roomName = this.server.getRoomName(this);
|
||||
boolean left = server.leaveRoom(this);
|
||||
sendPacket(new ServerResponsePacket(left ? Response.RoomLeft : Response.RoomNotLeft));
|
||||
if (left)
|
||||
onRoomLeave(roomName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(LoginPacket packet) {
|
||||
if (packet.getPseudo().isEmpty() || server.hasChatterName(packet.getPseudo())) {
|
||||
sendPacket(new ServerResponsePacket(Response.AuthError));
|
||||
return;
|
||||
}
|
||||
|
||||
this.chatterName = packet.getPseudo();
|
||||
sendPacket(new RoomListPacket(server.getRoomNames()));
|
||||
System.out.println("[Server] Chatter " + packet.getPseudo() + " connected !");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(RequestRoomListPacket packet) {
|
||||
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
|
||||
public void visitPacket(RoomListPacket packet) {
|
||||
// I'm never supposed to receive this from the client
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(ChatMessagePacket packet) {
|
||||
// I'm never supposed to receive this from the client
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(ServerResponsePacket packet) {
|
||||
// I'm never supposed to receive this from the client
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(RequestActualRoomPacket packet) {
|
||||
sendPacket(new ActualRoomPacket(server.getRoomName(this)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPacket(ActualRoomPacket packet) {
|
||||
// I'm never supposed to receive this from the client
|
||||
throw new UnsupportedOperationException("Unimplemented method 'visitPacket'");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user