diff --git a/.gitignore b/.gitignore index 9267e07..41874ab 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ gradle-app.setting # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 # gradle/wrapper/gradle-wrapper.properties /out/ +/clavardator_stored_files/ diff --git a/.idea/gradle.xml b/.idea/gradle.xml index a80c414..86ab5b3 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -11,6 +11,7 @@ diff --git a/client/config.json b/client/config.json index 7129e25..6cb0675 100644 --- a/client/config.json +++ b/client/config.json @@ -1,7 +1,7 @@ { "serveur": { "actif": 1, - "uri": "test", + "uri": "localhost", "type": "INSA", "ports": { "presence": 35650, diff --git a/client/src/main/java/fr/insa/clavardator/client/errors/UsernameTakenException.java b/client/src/main/java/fr/insa/clavardator/client/errors/UsernameTakenException.java deleted file mode 100644 index 21b6c37..0000000 --- a/client/src/main/java/fr/insa/clavardator/client/errors/UsernameTakenException.java +++ /dev/null @@ -1,9 +0,0 @@ -package fr.insa.clavardator.client.errors; - -import java.io.Serializable; - -public class UsernameTakenException extends Exception implements Serializable { - public UsernameTakenException(String message) { - super(message); - } -} diff --git a/client/src/main/java/fr/insa/clavardator/client/network/PeerHandshake.java b/client/src/main/java/fr/insa/clavardator/client/network/PeerHandshake.java index ce829cd..dece5c0 100644 --- a/client/src/main/java/fr/insa/clavardator/client/network/PeerHandshake.java +++ b/client/src/main/java/fr/insa/clavardator/client/network/PeerHandshake.java @@ -1,7 +1,7 @@ package fr.insa.clavardator.client.network; -import fr.insa.clavardator.client.errors.UsernameTakenException; import fr.insa.clavardator.client.users.CurrentUser; +import fr.insa.clavardator.lib.errors.UsernameTakenException; import fr.insa.clavardator.lib.network.TcpConnection; import fr.insa.clavardator.lib.users.UserInformation; import fr.insa.clavardator.lib.util.ErrorCallback; @@ -71,7 +71,7 @@ public class PeerHandshake { private void sendUsernameTaken(TcpConnection thisConnection) { Log.v(this.getClass().getSimpleName(), "Received username request using current username"); - thisConnection.send(new UsernameTakenException("Username taken"), this::closeConnection, null); + thisConnection.send(new UsernameTakenException("Username taken", userInformation.id), this::closeConnection, null); } diff --git a/client/src/main/java/fr/insa/clavardator/client/server/InsaPresence.java b/client/src/main/java/fr/insa/clavardator/client/server/InsaPresence.java index 9c0e4ad..13df66e 100644 --- a/client/src/main/java/fr/insa/clavardator/client/server/InsaPresence.java +++ b/client/src/main/java/fr/insa/clavardator/client/server/InsaPresence.java @@ -1,8 +1,8 @@ package fr.insa.clavardator.client.server; -import fr.insa.clavardator.lib.network.TcpConnection; import fr.insa.clavardator.client.users.CurrentUser; import fr.insa.clavardator.lib.message.Message; +import fr.insa.clavardator.lib.network.TcpConnection; import fr.insa.clavardator.lib.users.UserInformation; import fr.insa.clavardator.lib.util.ErrorCallback; import fr.insa.clavardator.lib.util.Log; @@ -83,14 +83,19 @@ public class InsaPresence implements Presence { final ArrayList list = (ArrayList) msg; for (Object obj : list) { if (obj instanceof UserInformation) { - userList.add((UserInformation)obj); + userList.add((UserInformation) obj); } } - } - Log.v(getClass().getSimpleName(), "Receive subscribe response: " + msg); - Log.v(getClass().getSimpleName(), "Converted list: " + Arrays.toString(userList.toArray())); - if (callback != null) { - callback.call(userList); + Log.v(getClass().getSimpleName(), "Receive subscribe response: " + msg); + Log.v(getClass().getSimpleName(), "Converted list: " + Arrays.toString(userList.toArray())); + if (callback != null) { + callback.call(userList); + } + } else { + Log.e(getClass().getSimpleName(), "Unexpected object received: " + msg.getClass().getSimpleName()); + if (errorCallback != null) { + errorCallback.onError(new RuntimeException("Unexpected object received")); + } } }, errorCallback); } @@ -133,13 +138,12 @@ public class InsaPresence implements Presence { */ private void connectToProxy(SimpleCallback callback, ErrorCallback errorCallback) { try { - proxyConnection = new TcpConnection(InetAddress.getByName(path), - proxyPort, - (newConnection) -> { + proxyConnection = new TcpConnection(InetAddress.getByName(path), proxyPort, + (newConnection) -> newConnection.send(new UserInformation(CurrentUser.getInstance()), () -> { if (callback != null) { callback.call(); } - }, + }, errorCallback), errorCallback); } catch (UnknownHostException e) { Log.e(getClass().getSimpleName(), "Could not connect to presence proxy", e); diff --git a/client/src/main/java/fr/insa/clavardator/client/users/PeerUser.java b/client/src/main/java/fr/insa/clavardator/client/users/PeerUser.java index 053a52a..4343431 100644 --- a/client/src/main/java/fr/insa/clavardator/client/users/PeerUser.java +++ b/client/src/main/java/fr/insa/clavardator/client/users/PeerUser.java @@ -1,10 +1,10 @@ package fr.insa.clavardator.client.users; import fr.insa.clavardator.client.chat.ChatHistory; +import fr.insa.clavardator.client.db.DatabaseController; +import fr.insa.clavardator.lib.errors.UsernameTakenException; import fr.insa.clavardator.lib.message.FileMessage; import fr.insa.clavardator.lib.message.Message; -import fr.insa.clavardator.client.db.DatabaseController; -import fr.insa.clavardator.client.errors.UsernameTakenException; import fr.insa.clavardator.lib.network.TcpConnection; import fr.insa.clavardator.lib.users.User; import fr.insa.clavardator.lib.users.UserInformation; @@ -102,7 +102,7 @@ public class PeerUser extends User implements Comparable { private void sendUsernameTaken(TcpConnection thisConnection) { Log.v(this.getClass().getSimpleName(), "Received username request using current username"); - thisConnection.send(new UsernameTakenException("Username taken"), this::disconnect, null); + thisConnection.send(new UsernameTakenException("Username taken", getId()), this::disconnect, null); } public void init(TcpConnection connection, String id, String username, ErrorCallback errorCallback) { diff --git a/client/src/main/java/fr/insa/clavardator/client/users/UserList.java b/client/src/main/java/fr/insa/clavardator/client/users/UserList.java index 9d13a33..97a0aba 100644 --- a/client/src/main/java/fr/insa/clavardator/client/users/UserList.java +++ b/client/src/main/java/fr/insa/clavardator/client/users/UserList.java @@ -54,7 +54,7 @@ public class UserList { if (savedUser.isActive()) { Log.v(getClass().getSimpleName(), "Received user from presence server already known and connected"); } else { - Log.v(getClass().getSimpleName(), "Received user from presence server already known and connected"); + Log.v(getClass().getSimpleName(), "Received user from presence server already known but not connected, connecting..."); savedUser.init(proxyConnection, userInfo.id, userInfo.getUsername(), null); } } else { diff --git a/lib/src/main/java/fr/insa/clavardator/lib/errors/UsernameTakenException.java b/lib/src/main/java/fr/insa/clavardator/lib/errors/UsernameTakenException.java new file mode 100644 index 0000000..af2c2ae --- /dev/null +++ b/lib/src/main/java/fr/insa/clavardator/lib/errors/UsernameTakenException.java @@ -0,0 +1,12 @@ +package fr.insa.clavardator.lib.errors; + +import java.io.Serializable; + +public class UsernameTakenException extends Exception implements Serializable { + public final String recipient; + + public UsernameTakenException(String message, String recipient) { + super(message); + this.recipient = recipient; + } +} diff --git a/lib/src/main/java/fr/insa/clavardator/lib/network/TcpListener.java b/lib/src/main/java/fr/insa/clavardator/lib/network/TcpListener.java index fb5ce7b..d32eb86 100644 --- a/lib/src/main/java/fr/insa/clavardator/lib/network/TcpListener.java +++ b/lib/src/main/java/fr/insa/clavardator/lib/network/TcpListener.java @@ -10,10 +10,16 @@ import static fr.insa.clavardator.lib.network.TcpConnection.TCP_PORT; public class TcpListener { Acceptor acceptor = null; + int port = TCP_PORT; public TcpListener() { } + public TcpListener(int port) { + this.port = port; + } + + /** * Start accepting incoming connections * @@ -24,7 +30,7 @@ public class TcpListener { if (acceptor != null) { acceptor.stopAccepting(); } - acceptor = new Acceptor(callback, errorCallback); + acceptor = new Acceptor(port, callback, errorCallback); acceptor.start(); } @@ -43,8 +49,10 @@ public class TcpListener { private final NewConnectionCallback callback; private final ErrorCallback errorCallback; private ServerSocket server; + private int port; - public Acceptor(NewConnectionCallback callback, ErrorCallback errorCallback) { + public Acceptor(int port, NewConnectionCallback callback, ErrorCallback errorCallback) { + this.port = port; this.callback = callback; this.errorCallback = errorCallback; } @@ -52,7 +60,7 @@ public class TcpListener { @Override public void run() { try { - server = new ServerSocket(TCP_PORT); + server = new ServerSocket(port); while (!shouldStop) { Socket clientSocket = server.accept(); callback.onNewConnection(clientSocket); diff --git a/server/build.gradle b/server/build.gradle new file mode 100644 index 0000000..15511c3 --- /dev/null +++ b/server/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'application' + id 'com.github.johnrengelman.shadow' version '6.1.0' +} + +group 'fr.insa.clavardator.server' +version '0.0.1' + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':lib') + implementation 'org.jetbrains:annotations:20.1.0' +} + +mainClassName = 'fr.insa.clavardator.server.Main' \ No newline at end of file diff --git a/server/src/main/java/fr/insa/clavardator/server/Main.java b/server/src/main/java/fr/insa/clavardator/server/Main.java new file mode 100644 index 0000000..a122782 --- /dev/null +++ b/server/src/main/java/fr/insa/clavardator/server/Main.java @@ -0,0 +1,24 @@ +package fr.insa.clavardator.server; + +import fr.insa.clavardator.lib.util.Log; + +import java.util.Scanner; + +public class Main { + + public static void main(String[] args) { + Log.v(Main.class.getSimpleName(), "Server started! You can stop it by typing stop"); + + Proxy proxy = new Proxy(); + Presence presence = new Presence(); + + String line; + Scanner scanner = new Scanner(System.in); + do { + line = scanner.nextLine(); + } while (!line.equals("stop")); + + proxy.stop(); + presence.stop(); + } +} diff --git a/server/src/main/java/fr/insa/clavardator/server/Presence.java b/server/src/main/java/fr/insa/clavardator/server/Presence.java new file mode 100644 index 0000000..32321b8 --- /dev/null +++ b/server/src/main/java/fr/insa/clavardator/server/Presence.java @@ -0,0 +1,87 @@ +package fr.insa.clavardator.server; + +import fr.insa.clavardator.lib.network.TcpConnection; +import fr.insa.clavardator.lib.network.TcpListener; +import fr.insa.clavardator.lib.users.UserInformation; +import fr.insa.clavardator.lib.util.Log; + +import java.io.EOFException; +import java.io.Serializable; +import java.net.Socket; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class Presence { + private static final int PRESENCE_PORT = 35650; + + private final Map subscribers = new HashMap<>(); + private final ArrayList connectedUsers = new ArrayList<>(); + private final TcpListener presenceListener; + + public Presence() { + presenceListener = new TcpListener(PRESENCE_PORT); + presenceListener.acceptConnection(this::subscribe, + e -> Log.e(getClass().getSimpleName(), "Error while registering a user", e)); + } + + public void publish(UserInformation info) { + for (TcpConnection subscriber : subscribers.values()) { + ArrayList msg = new ArrayList<>(1); + msg.add(info); + subscriber.send(msg, null, + e -> Log.e(getClass().getSimpleName(), "Error while publishing user information", e)); + } + } + + public void subscribe(Socket socket) { + TcpConnection user = new TcpConnection(socket); + + // Receive user information + user.receiveOne(o -> { + if (o instanceof UserInformation) { + UserInformation userInformation = ((UserInformation) o); + Log.v(getClass().getSimpleName(), "Registering user " + userInformation.id + + " (" + userInformation.getUsername() + ")"); + + // publish new user info to all subscribers + publish(userInformation); + + // Send the list of connected users to the new subscriber. We're cloning it because we're modifying it + // just after this call, while the sender thread might not have send it yet + user.send((Serializable) connectedUsers.clone(), null, + e -> Log.e(getClass().getSimpleName(), "Error while receiving user information", e)); + + subscribers.put(userInformation.id, user); + connectedUsers.add(userInformation); + + user.receive(msg -> Log.w(getClass().getSimpleName(), "Received an unexpected message " + msg), + e -> { + if (e instanceof EOFException) { + unsubscribe(userInformation.id); + } + }); + } else { + Log.e(getClass().getSimpleName(), "Received unexpected message type: " + o.getClass().getSimpleName()); + } + }, e -> { + if (!(e instanceof EOFException)) { + Log.e(getClass().getSimpleName(), "Error while receiving user information", e); + } + }); + } + + public void unsubscribe(String id) { + Log.v(getClass().getSimpleName(), "Unsubscribing user " + id); + subscribers.get(id).close(); + subscribers.remove(id); + connectedUsers.removeIf(userInformation -> userInformation.id.equals(id)); + } + + public void stop() { + presenceListener.stopAccepting(); + for (TcpConnection connection : subscribers.values()) { + connection.close(); + } + } +} diff --git a/server/src/main/java/fr/insa/clavardator/server/Proxy.java b/server/src/main/java/fr/insa/clavardator/server/Proxy.java new file mode 100644 index 0000000..f9aebc9 --- /dev/null +++ b/server/src/main/java/fr/insa/clavardator/server/Proxy.java @@ -0,0 +1,80 @@ +package fr.insa.clavardator.server; + +import fr.insa.clavardator.lib.errors.UsernameTakenException; +import fr.insa.clavardator.lib.message.Message; +import fr.insa.clavardator.lib.network.TcpConnection; +import fr.insa.clavardator.lib.network.TcpListener; +import fr.insa.clavardator.lib.users.UserInformation; +import fr.insa.clavardator.lib.util.Log; + +import java.io.EOFException; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +public class Proxy { + private static final int PROXY_PORT = 35750; + private final HashMap users = new HashMap<>(); + private final TcpListener proxyListener; + + public Proxy() { + proxyListener = new TcpListener(PROXY_PORT); + proxyListener.acceptConnection(clientSocket -> { + + Log.v(getClass().getSimpleName(), "Accepting a new user"); + TcpConnection connection = new TcpConnection(clientSocket); + connection.receive(msg -> { + + if (msg instanceof Message) { + Log.v(getClass().getSimpleName(), "Transmitting message: " + msg); + transmitMessage((Serializable) msg, ((Message) msg).getRecipient().id); + + } else if (msg instanceof UsernameTakenException) { + UsernameTakenException unameTaken = ((UsernameTakenException) msg); + transmitMessage(unameTaken, unameTaken.recipient); + + } else if (msg instanceof UserInformation) { + Log.v(getClass().getSimpleName(), "Registering new user: " + msg); + users.put(((UserInformation) msg).id, connection); + for (String userId : users.keySet()) { + UserInformation userInfo = ((UserInformation) msg); + if (!userId.equals(userInfo.id)) { + transmitMessage((Serializable) msg, userId); + } + } + } + + }, e -> { + if (e instanceof EOFException) { + for (Map.Entry user : users.entrySet()) { + if (user.getValue().equals(connection)) { + Log.v(getClass().getSimpleName(), "Disconnecting user " + user.getKey()); + users.remove(user.getKey()); + break; + } + } + } else { + Log.e(getClass().getSimpleName(), "Error while receiving message to transmit", e); + } + }); + }, + e -> Log.e(getClass().getSimpleName(), "Error while accepting a user", e)); + } + + + void transmitMessage(Serializable msg, String recipientId) { + TcpConnection user = users.get(recipientId); + if (user == null) { + Log.e(getClass().getSimpleName(), "Cannot find the recipient in the connected users"); + } else { + user.send(msg, null, e -> Log.e(getClass().getSimpleName(), "Error while sending message", e)); + } + } + + public void stop() { + proxyListener.stopAccepting(); + for (TcpConnection connection : users.values()) { + connection.close(); + } + } +} diff --git a/settings.gradle b/settings.gradle index 8449312..67eb568 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,4 @@ rootProject.name = 'clavardator' include 'client' include 'lib' +include 'server'