Implement basic presence server functionality

This commit is contained in:
Arnaud Vergnet 2021-01-07 17:04:45 +01:00
parent 1cf1ebec37
commit 68125c7c92
5 changed files with 211 additions and 71 deletions

View file

@ -13,7 +13,7 @@ import javafx.stage.Stage;
public class MainApp extends Application {
private UserList userList;
private MainController mainController;
public static void main(String[] args) {
launch(args);
@ -26,9 +26,7 @@ public class MainApp extends Application {
final FXMLLoader mainLoader = new FXMLLoader(getClass().getResource("ui/scene.fxml"));
final Parent content = mainLoader.load();
MainController mainController = mainLoader.getController();
userList = mainController.getUserList();
mainController = mainLoader.getController();
Scene scene = new Scene(content);
@ -43,8 +41,8 @@ public class MainApp extends Application {
@Override
public void stop() throws Exception {
// Stop all threads and active connections before exiting
if (userList != null) {
userList.destroy();
if (mainController != null) {
mainController.stop();
}
super.stop();
}

View file

@ -1,11 +1,16 @@
package fr.insa.clavardator.server;
import fr.insa.clavardator.network.TcpConnection;
import fr.insa.clavardator.users.CurrentUser;
import fr.insa.clavardator.users.UserInformation;
import fr.insa.clavardator.util.ErrorCallback;
import fr.insa.clavardator.util.Log;
import fr.insa.clavardator.util.ParametrizedCallback;
import fr.insa.clavardator.util.SimpleCallback;
import org.jetbrains.annotations.Nullable;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
/**
@ -29,6 +34,10 @@ public class InsaPresence implements Presence {
private final int presencePort;
private final int proxyPort;
private TcpConnection presenceConnection;
private TcpConnection proxyConnection;
public InsaPresence(String path, int presencePort, int proxyPort) {
this.path = path;
this.presencePort = presencePort;
@ -37,21 +46,131 @@ public class InsaPresence implements Presence {
@Override
public void subscribe(ParametrizedCallback<ArrayList<UserInformation>> callback, ErrorCallback errorCallback) {
if (!isConnected()) {
connectToPresence(
() -> connectToProxy(() -> {
sendSubscribeMessage(errorCallback);
receiveSubscribeNotifications(callback, errorCallback);
}, errorCallback),
errorCallback);
} else {
Log.v(getClass().getSimpleName(), "Already subscribed to presence server");
}
}
/**
* Send current user information to tell the server who is subscribing
*
* @param errorCallback Called on connection error
*/
private void sendSubscribeMessage(ErrorCallback errorCallback) {
presenceConnection.send(new UserInformation(CurrentUser.getInstance()), null, errorCallback);
}
/**
* Waits for presence server response to the subscribe request
*
* @param callback Called when the message is successfully received
* @param errorCallback Called on connection error
*/
private void receiveSubscribeNotifications(ParametrizedCallback<ArrayList<UserInformation>> callback, @Nullable ErrorCallback errorCallback) {
presenceConnection.receive(
msg -> {
// TODO decide what we should receive
final ArrayList<UserInformation> test = new ArrayList<>();
test.add(new UserInformation("test", "test"));
Log.v(getClass().getSimpleName(), "Receive subscribe response: " + msg);
if (callback != null) {
callback.call(test);
}
}, errorCallback);
}
@Override
public void unsubscribe(SimpleCallback callback, ErrorCallback errorCallback) {
public void unsubscribe(SimpleCallback callback, @Nullable ErrorCallback errorCallback) {
if (isConnected()) {
disconnect();
} else {
Log.v(getClass().getSimpleName(), "Not subscribed to presence server");
}
}
@Override
public void publish(boolean connected, SimpleCallback callback, ErrorCallback errorCallback) {
/**
* Connects to the presence server by TCP
*
* @param callback Called when connection is successful
* @param errorCallback Called on connection error
*/
private void connectToPresence(SimpleCallback callback, ErrorCallback errorCallback) {
try {
presenceConnection = new TcpConnection(InetAddress.getByName(path),
presencePort,
(newConnection) -> {
if (callback != null) {
callback.call();
}
},
errorCallback);
} catch (UnknownHostException e) {
Log.e(getClass().getSimpleName(), "Could not connect to presence server", e);
}
}
/**
* Connects to the presence proxy by TCP
*
* @param callback Called when connection is successful
* @param errorCallback Called on connection error
*/
private void connectToProxy(SimpleCallback callback, ErrorCallback errorCallback) {
try {
proxyConnection = new TcpConnection(InetAddress.getByName(path),
proxyPort,
(newConnection) -> {
if (callback != null) {
callback.call();
}
},
errorCallback);
} catch (UnknownHostException e) {
Log.e(getClass().getSimpleName(), "Could not connect to presence proxy", e);
}
}
/**
* Closes the given connection
*/
private void closeConnection(@Nullable TcpConnection c) {
if (c != null && c.isOpen()) {
c.close();
}
}
/**
* Checks if we are connected to the presence server and proxy
*
* @return True only if we are connected to both presence server and proxy
*/
private boolean isConnected() {
return presenceConnection != null &&
presenceConnection.isOpen() &&
proxyConnection != null &&
proxyConnection.isOpen();
}
/**
* Disconnects from both presence server and proxy
*/
private void disconnect() {
Log.v(this.getClass().getSimpleName(), "Disconnecting presence server");
closeConnection(presenceConnection);
closeConnection(proxyConnection);
presenceConnection = null;
proxyConnection = null;
}
@Override
public TcpConnection getProxyConnection() {
return null;
return proxyConnection;
}
}

View file

@ -5,6 +5,7 @@ import fr.insa.clavardator.users.UserInformation;
import fr.insa.clavardator.util.ErrorCallback;
import fr.insa.clavardator.util.ParametrizedCallback;
import fr.insa.clavardator.util.SimpleCallback;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
@ -23,7 +24,7 @@ public interface Presence {
*
* @param callback Called when subscription completes
*/
void subscribe(ParametrizedCallback<ArrayList<UserInformation>> callback, ErrorCallback errorCallback);
void subscribe(ParametrizedCallback<ArrayList<UserInformation>> callback, @Nullable ErrorCallback errorCallback);
/**
* Stops subscription to the presence server by closing TCP connections.
@ -32,17 +33,7 @@ public interface Presence {
* @implNote Call this before exiting the app.
* If not, the presence server will wait for a tcp timeout before marking this user as disconnected.
*/
void unsubscribe(SimpleCallback callback, ErrorCallback errorCallback);
/**
* Updates the current user state on the server.
* This function must be called on app exit,
* or the server won't know this user is inactive.
*
* @param connected The new user state
*/
void publish(boolean connected, SimpleCallback callback, ErrorCallback errorCallback);
void unsubscribe(SimpleCallback callback, @Nullable ErrorCallback errorCallback);
/**
* Gets a connection to the proxy.

View file

@ -2,6 +2,7 @@ package fr.insa.clavardator.ui;
import com.jfoenix.controls.JFXSnackbar;
import fr.insa.clavardator.config.Config;
import fr.insa.clavardator.config.ConfigLoader;
import fr.insa.clavardator.db.DatabaseController;
import fr.insa.clavardator.server.Presence;
import fr.insa.clavardator.server.PresenceFactory;
@ -14,7 +15,6 @@ import fr.insa.clavardator.ui.dialogs.SnackbarController;
import fr.insa.clavardator.ui.users.UserListController;
import fr.insa.clavardator.users.CurrentUser;
import fr.insa.clavardator.users.UserList;
import fr.insa.clavardator.config.ConfigLoader;
import fr.insa.clavardator.util.Log;
import javafx.application.Platform;
import javafx.fxml.FXML;
@ -180,6 +180,7 @@ public class MainController implements Initializable {
Log.v(this.getClass().getSimpleName(), "Chat started");
discoverActiveUsers();
startListening();
subscribeToPresenceServer();
Platform.runLater(this::showChat);
}
@ -190,9 +191,7 @@ public class MainController implements Initializable {
online = false;
listController.setRefreshButtonEnabled(false);
Log.v(this.getClass().getSimpleName(), "Chat ended");
if (userList != null) {
userList.destroy();
}
stop();
}
/**
@ -251,23 +250,71 @@ public class MainController implements Initializable {
private void initBackend() {
ConfigLoader.load(
(Config config) -> new DatabaseController().initTables(
() -> userList.retrievedPreviousUsers(
() -> currentUser.init(
() -> userList.initPresenceServer(config),
this::onInitError),
this::onInitError
), this::onInitError));
() -> userList.retrievedPreviousUsers(
() -> currentUser.init(
() -> initPresenceServer(config),
this::onInitError),
this::onInitError
), this::onInitError));
}
/**
* Gets the current user list.
* Initializes the presence server based on user config
*
* @implNote BE SURE TO CALL .destroy() BEFORE EXITING THE APP.
*
* @return The current user list
* @param config The user config
*/
public UserList getUserList() {
return userList;
public void initPresenceServer(Config config) {
final Config.ServerConfig serverConfig = config.getServerConfig();
if (serverConfig.isEnabled()) {
try {
final PresenceType type = serverConfig.getType();
final String uri = serverConfig.getUri();
final int presencePort = serverConfig.getPresencePort();
final int proxyPort = serverConfig.getProxyPort();
presenceServer = PresenceFactory.create(type, uri, presencePort, proxyPort);
Log.v(getClass().getSimpleName(), "Presence server support enabled: " + type + "@" + uri + ':' + presencePort + " / proxy:" + proxyPort);
subscribeToPresenceServer();
} catch (UnknownPresenceException e) {
Log.e(getClass().getSimpleName(), "Presence server type not found", e);
presenceServer = null;
}
} else {
Log.v(getClass().getSimpleName(), "Presence server support disabled.");
}
}
private void subscribeToPresenceServer() {
if (presenceServer != null && online) {
presenceServer.subscribe(
param -> userList.onReceivePresenceNotification(
param,
presenceServer.getProxyConnection()),
(e) -> Log.v(
getClass().getSimpleName(),
"Error subscribing to presence server",
e));
}
}
private void unsubscribeToPresenceServer() {
if (presenceServer != null) {
presenceServer.unsubscribe(
null,
(e) -> Log.v(
getClass().getSimpleName(),
"Error unsubscribing to presence server",
e));
}
}
/**
* Stop all threads and active connections before exiting
*/
public void stop() {
if (userList != null) {
userList.destroy();
}
unsubscribeToPresenceServer();
}
@Override

View file

@ -1,20 +1,17 @@
package fr.insa.clavardator.users;
import fr.insa.clavardator.config.Config;
import fr.insa.clavardator.db.DatabaseController;
import fr.insa.clavardator.network.TcpListener;
import fr.insa.clavardator.network.NetDiscoverer;
import fr.insa.clavardator.network.PeerHandshake;
import fr.insa.clavardator.server.Presence;
import fr.insa.clavardator.server.PresenceFactory;
import fr.insa.clavardator.server.PresenceType;
import fr.insa.clavardator.server.UnknownPresenceException;
import fr.insa.clavardator.network.TcpConnection;
import fr.insa.clavardator.network.TcpListener;
import fr.insa.clavardator.util.ErrorCallback;
import fr.insa.clavardator.util.Log;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
@ -29,8 +26,6 @@ public class UserList {
private final NetDiscoverer netDiscoverer = new NetDiscoverer();
private final TcpListener tcpListener = new TcpListener();
private Presence presenceServer;
public UserList() {
}
@ -50,6 +45,20 @@ public class UserList {
}, errorCallback);
}
public void onReceivePresenceNotification(ArrayList<UserInformation> newPresenceUsers, TcpConnection proxyConnection) {
newPresenceUsers.forEach((userInfo -> {
final PeerUser savedUser = userHashmap.get(userInfo.id);
if (savedUser != null) {
Log.v(getClass().getSimpleName(), "Received user from presence server already known");
} else {
final PeerUser user = new PeerUser();
user.init(proxyConnection, userInfo.id, userInfo.getUsername(), null);
userHashmap.put(user.id, user);
Platform.runLater(() -> userObservableList.add(user));
}
}));
}
/**
* Starts listening for other broadcasts.
* Only answers and waits for them to initiate the connection.
@ -132,30 +141,6 @@ public class UserList {
userHashmap.forEach((id, user) -> user.sendCurrentUser(Throwable::printStackTrace));
}
/**
* Initializes the presence server based on user config
*
* @param config The user config
*/
public void initPresenceServer(Config config) {
final Config.ServerConfig serverConfig = config.getServerConfig();
if (serverConfig.isEnabled()) {
try {
final PresenceType type = serverConfig.getType();
final String uri = serverConfig.getUri();
final int presencePort = serverConfig.getPresencePort();
final int proxyPort = serverConfig.getProxyPort();
presenceServer = PresenceFactory.create(type, uri, presencePort, proxyPort);
Log.v(getClass().getSimpleName(), "Presence server support enabled: " + type + "@" + uri + ':' + presencePort + " / proxy:" + proxyPort);
} catch (UnknownPresenceException e) {
Log.e(getClass().getSimpleName(), "Presence server type not found", e);
presenceServer = null;
}
} else {
Log.v(getClass().getSimpleName(), "Presence server support disabled.");
}
}
/**
* Closes all running threads, sockets and db connection.
* Must be called before exiting the app.