Implement basic presence server functionality
This commit is contained in:
parent
1cf1ebec37
commit
68125c7c92
5 changed files with 211 additions and 71 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue