Browse Source

Simplify user connection

Extracted handshake phase into separate class.
Arnaud Vergnet 3 years ago
parent
commit
63cb83916a

+ 5
- 5
src/main/java/fr/insa/clavardator/chat/ChatHistory.java View File

@@ -63,8 +63,10 @@ public class ChatHistory {
63 63
 	 */
64 64
 	private void onLoaded(ArrayList<Message> newHistory) {
65 65
 		historyLoaded = true;
66
-		history.addAll(newHistory);
67
-		history.sort((message, t1) -> (int) (message.getDate().getTime() - t1.getDate().getTime()));
66
+		Platform.runLater(() -> {
67
+			history.addAll(newHistory);
68
+			history.sort((message, t1) -> (int) (message.getDate().getTime() - t1.getDate().getTime()));
69
+		});
68 70
 		Log.v(getClass().getSimpleName(), "Message history loaded");
69 71
 		notifyHistoryLoaded();
70 72
 	}
@@ -79,9 +81,7 @@ public class ChatHistory {
79 81
 	 * @param message The message to add
80 82
 	 */
81 83
 	public void addMessage(Message message, ErrorCallback errorCallback) {
82
-		db.addMessage(message, () -> {
83
-			Platform.runLater(() -> history.add(message));
84
-		}, errorCallback);
84
+		db.addMessage(message, () -> Platform.runLater(() -> history.add(message)), errorCallback);
85 85
 	}
86 86
 
87 87
 	public interface HistoryLoadedCallback {

+ 98
- 0
src/main/java/fr/insa/clavardator/network/PeerHandshake.java View File

@@ -0,0 +1,98 @@
1
+package fr.insa.clavardator.network;
2
+
3
+import fr.insa.clavardator.errors.UsernameTakenException;
4
+import fr.insa.clavardator.users.CurrentUser;
5
+import fr.insa.clavardator.users.UserInformation;
6
+import fr.insa.clavardator.util.ErrorCallback;
7
+import fr.insa.clavardator.util.Log;
8
+
9
+import java.net.InetAddress;
10
+import java.net.Socket;
11
+
12
+public class PeerHandshake {
13
+
14
+	private PeerConnection connection;
15
+	private UserInformation userInformation;
16
+
17
+	public void createConnection(InetAddress ipAddr, UserConnectedCallback callback, ErrorCallback errorCallback) {
18
+		closeConnection();
19
+		Log.v(this.getClass().getSimpleName(), "Creating new TCP connection ");
20
+
21
+		connection = new PeerConnection(
22
+				ipAddr,
23
+				(thisConnection) -> init(thisConnection,
24
+						false,
25
+						callback,
26
+						errorCallback),
27
+				e -> {
28
+					Log.e(this.getClass().getSimpleName(), "Could not create TCP connection", e);
29
+					closeConnection();
30
+					errorCallback.onError(e);
31
+				});
32
+	}
33
+
34
+	public void acceptConnection(Socket socket, UserConnectedCallback callback, ErrorCallback errorCallback) {
35
+		closeConnection();
36
+		connection = new PeerConnection(socket);
37
+		init(connection, true, callback, errorCallback);
38
+	}
39
+
40
+	private void init(PeerConnection thisConnection, boolean isReceiving, UserConnectedCallback callback, ErrorCallback errorCallback) {
41
+		// Send our username
42
+		thisConnection.send(new UserInformation(CurrentUser.getInstance()), null, e -> {
43
+			closeConnection();
44
+			errorCallback.onError(e);
45
+		});
46
+
47
+		// Receive peer's username
48
+		thisConnection.receiveOne(msg -> {
49
+			if (msg instanceof UserInformation) {
50
+				UserInformation userInfo = (UserInformation) msg;
51
+				final String receivedUsername = userInfo.getUsername();
52
+				if (!receivedUsername.equals(CurrentUser.getInstance().getUsername())) {
53
+					this.userInformation = userInfo;
54
+					callback.onUserConnected(this);
55
+				} else if (isReceiving) {
56
+					sendUsernameTaken(thisConnection);
57
+				} else {
58
+					closeConnection();
59
+					errorCallback.onError(new Exception("Tried to use same username as remote"));
60
+				}
61
+			} else {
62
+				closeConnection();
63
+				errorCallback.onError(new Exception("Did not receive remote username"));
64
+			}
65
+		}, e -> {
66
+			closeConnection();
67
+			errorCallback.onError(e);
68
+		});
69
+	}
70
+
71
+	private void sendUsernameTaken(PeerConnection thisConnection) {
72
+		Log.v(this.getClass().getSimpleName(), "Received username request using current username");
73
+		thisConnection.send(new UsernameTakenException("Username taken"), this::closeConnection, null);
74
+	}
75
+
76
+
77
+	/**
78
+	 * Close the connection to this user
79
+	 */
80
+	private void closeConnection() {
81
+		if (connection != null && connection.isOpen()) {
82
+			connection.close();
83
+			connection = null;
84
+		}
85
+	}
86
+
87
+	public PeerConnection getConnection() {
88
+		return connection;
89
+	}
90
+
91
+	public UserInformation getUserInformation() {
92
+		return userInformation;
93
+	}
94
+
95
+	public interface UserConnectedCallback {
96
+		void onUserConnected(PeerHandshake handshake);
97
+	}
98
+}

+ 2
- 4
src/main/java/fr/insa/clavardator/ui/chat/MessageListItemCell.java View File

@@ -30,10 +30,8 @@ public class MessageListItemCell extends ListCell<Message> {
30 30
 		if (item == null || empty) {
31 31
 			setGraphic(null);
32 32
 		} else {
33
-				Platform.runLater(() -> {
34
-					setGraphic(view);
35
-					messageListItemController.setMessage(item);
36
-				});
33
+			setGraphic(view);
34
+			messageListItemController.setMessage(item);
37 35
 		}
38 36
 	}
39 37
 }

+ 1
- 18
src/main/java/fr/insa/clavardator/ui/users/UserListController.java View File

@@ -6,8 +6,6 @@ import fr.insa.clavardator.ui.UserSelectedEvent;
6 6
 import fr.insa.clavardator.users.PeerUser;
7 7
 import fr.insa.clavardator.users.User;
8 8
 import fr.insa.clavardator.users.UserList;
9
-import fr.insa.clavardator.util.Log;
10
-import javafx.application.Platform;
11 9
 import javafx.fxml.FXML;
12 10
 import javafx.fxml.Initializable;
13 11
 import javafx.scene.control.ListView;
@@ -64,25 +62,10 @@ public class UserListController implements Initializable {
64 62
 	}
65 63
 
66 64
 	/**
67
-	 * Add user to UI if not already present
68
-	 * @param user The new user to display
69
-	 */
70
-	private void onUserConnected(PeerUser user) {
71
-		Platform.runLater(() -> {
72
-			if (!peerUserListView.getItems().contains(user)) {
73
-				Log.v(this.getClass().getSimpleName(), "Add user to UI");
74
-				peerUserListView.getItems().add(user);
75
-			} else {
76
-				Log.w(this.getClass().getSimpleName(), "User already added to ui, skipping...");
77
-			}
78
-		});
79
-	}
80
-
81
-	/**
82 65
 	 * Sets the user list to subscribe to
83 66
 	 * @param userList The user list to use
84 67
 	 */
85 68
 	public void setUserList(UserList userList) {
86
-		userList.setNewUserObserver(this::onUserConnected);
69
+		peerUserListView.setItems(userList.getUserObservableList());
87 70
 	}
88 71
 }

+ 7
- 86
src/main/java/fr/insa/clavardator/users/PeerUser.java View File

@@ -11,8 +11,6 @@ import org.jetbrains.annotations.NotNull;
11 11
 import org.jetbrains.annotations.Nullable;
12 12
 
13 13
 import java.io.EOFException;
14
-import java.net.InetAddress;
15
-import java.net.Socket;
16 14
 import java.util.Date;
17 15
 
18 16
 public class PeerUser extends User implements Comparable<PeerUser> {
@@ -35,41 +33,6 @@ public class PeerUser extends User implements Comparable<PeerUser> {
35 33
 		history = new ChatHistory(this);
36 34
 	}
37 35
 
38
-	/**
39
-	 * Asynchronously connects to the user and receives its information (id, username)
40
-	 *
41
-	 * @param ipAddr        The IP address of this user
42
-	 * @param callback      The function to call on success
43
-	 * @param errorCallback The function to call on socket error
44
-	 */
45
-	public void createConnection(InetAddress ipAddr, UserConnectedCallback callback, ErrorCallback errorCallback) {
46
-		closeConnection();
47
-		Log.v(this.getClass().getSimpleName(), "Creating new TCP connection with " + id);
48
-		// Connect to the peer
49
-		setState(State.CONNECTING);
50
-		connection = new PeerConnection(
51
-				ipAddr,
52
-				(thisConnection) -> init(thisConnection, false, callback, errorCallback),
53
-				e -> {
54
-					Log.e(this.getClass().getSimpleName(), "Could not create TCP connection with " + id, e);
55
-					disconnect();
56
-					errorCallback.onError(e);
57
-				});
58
-	}
59
-
60
-	/**
61
-	 * Asynchronously connects to the user using the socket and receives its information (id, username)
62
-	 *
63
-	 * @param socket        A socket already connected to the user
64
-	 * @param callback      The function to call on success, with the new ActiveUser as parameter
65
-	 * @param errorCallback The function to call on socket error
66
-	 */
67
-	public void acceptConnection(Socket socket, UserConnectedCallback callback, ErrorCallback errorCallback) {
68
-		closeConnection();
69
-		setState(State.CONNECTING);
70
-		connection = new PeerConnection(socket);
71
-		init(connection, true, callback, errorCallback);
72
-	}
73 36
 
74 37
 	/**
75 38
 	 * Sends a basic text message to this user
@@ -112,46 +75,12 @@ public class PeerUser extends User implements Comparable<PeerUser> {
112 75
 		thisConnection.send(new UsernameTakenException("Username taken"), this::disconnect, null);
113 76
 	}
114 77
 
115
-	/**
116
-	 * Initializes the connection with this user.
117
-	 * Both user exchange their information
118
-	 *
119
-	 * @param thisConnection The peer connection to use
120
-	 * @param callback       Callback on success
121
-	 * @param errorCallback  Callback on error
122
-	 */
123
-	private void init(PeerConnection thisConnection, boolean isReceiving, UserConnectedCallback callback, ErrorCallback errorCallback) {
124
-		// Send our username
125
-		thisConnection.send(new UserInformation(CurrentUser.getInstance()), null, e -> {
126
-			disconnect();
127
-			errorCallback.onError(e);
128
-		});
129
-
130
-		// Receive peer's username
131
-		thisConnection.receiveOne(msg -> {
132
-			if (msg instanceof UserInformation) {
133
-				UserInformation userInfo = (UserInformation) msg;
134
-				final String receivedUsername = userInfo.getUsername();
135
-				if (!receivedUsername.equals(CurrentUser.getInstance().getUsername())) {
136
-					setId(userInfo.id);
137
-					setUsername(receivedUsername);
138
-					callback.onUserConnected();
139
-					subscribeToMessages(e -> {
140
-						disconnect();
141
-						errorCallback.onError(e);
142
-					});
143
-					setState(State.CONNECTED);
144
-				} else if (isReceiving) {
145
-					sendUsernameTaken(thisConnection);
146
-				} else {
147
-					disconnect();
148
-					errorCallback.onError(new Exception("Tried to use same username as remote"));
149
-				}
150
-			} else {
151
-				disconnect();
152
-				errorCallback.onError(new Exception("Did not receive remote username"));
153
-			}
154
-		}, e -> {
78
+	public void init(PeerConnection connection, String id, String username, ErrorCallback errorCallback) {
79
+		this.connection = connection;
80
+		this.id = id;
81
+		setUsername(username);
82
+		setState(State.CONNECTED);
83
+		subscribeToMessages((e) -> {
155 84
 			disconnect();
156 85
 			errorCallback.onError(e);
157 86
 		});
@@ -240,7 +169,7 @@ public class PeerUser extends User implements Comparable<PeerUser> {
240 169
 	 *
241 170
 	 * @param state The new state
242 171
 	 */
243
-	private void setState(State state) {
172
+	protected void setState(State state) {
244 173
 		pcs.firePropertyChange("state", this.state, state);
245 174
 		this.state = state;
246 175
 	}
@@ -268,15 +197,7 @@ public class PeerUser extends User implements Comparable<PeerUser> {
268 197
 	 * The user connection state
269 198
 	 */
270 199
 	public enum State {
271
-		CONNECTING,
272 200
 		CONNECTED,
273 201
 		DISCONNECTED,
274 202
 	}
275
-
276
-	/**
277
-	 * Callback when this user successfully connects
278
-	 */
279
-	public interface UserConnectedCallback {
280
-		void onUserConnected();
281
-	}
282 203
 }

+ 32
- 155
src/main/java/fr/insa/clavardator/users/UserList.java View File

@@ -3,24 +3,23 @@ package fr.insa.clavardator.users;
3 3
 import fr.insa.clavardator.db.DatabaseController;
4 4
 import fr.insa.clavardator.network.ConnectionListener;
5 5
 import fr.insa.clavardator.network.NetDiscoverer;
6
+import fr.insa.clavardator.network.PeerHandshake;
6 7
 import fr.insa.clavardator.util.ErrorCallback;
7 8
 import fr.insa.clavardator.util.Log;
9
+import javafx.application.Platform;
10
+import javafx.collections.FXCollections;
11
+import javafx.collections.ObservableList;
8 12
 
9
-import java.beans.PropertyChangeEvent;
10
-import java.util.ArrayList;
11
-import java.util.Arrays;
12 13
 import java.util.HashMap;
13 14
 import java.util.Map;
14 15
 import java.util.function.Predicate;
15 16
 
16 17
 public class UserList {
17
-
18
-	private final Map<String, PeerUser> inactiveUsers = new HashMap<>();
19
-	private final Map<String, PeerUser> activeUsers = new HashMap<>();
20
-	private final ArrayList<PeerUser> pendingUsers = new ArrayList<>();
21
-
22
-	private UserConnectionCallback newUsersObservers = null;
23
-
18
+	// List kept in sync with the running thread
19
+	private final Map<String, PeerUser> userHashmap = new HashMap<>();
20
+	// List kept in sync with ui thread, may be out out date with rapid interactions
21
+	// Use only for display, not for duplicate checking
22
+	private final ObservableList<PeerUser> userObservableList = FXCollections.observableArrayList();
24 23
 	private final DatabaseController db = new DatabaseController();
25 24
 	private final NetDiscoverer netDiscoverer = new NetDiscoverer();
26 25
 	private final ConnectionListener connectionListener = new ConnectionListener();
@@ -29,40 +28,18 @@ public class UserList {
29 28
 	}
30 29
 
31 30
 	/**
32
-	 * Adds an observer for new user connection events
33
-	 *
34
-	 * @param connectionCallback The function to add as listener
35
-	 */
36
-	public void setNewUserObserver(UserConnectionCallback connectionCallback) {
37
-		newUsersObservers = connectionCallback;
38
-	}
39
-
40
-	/**
41
-	 * Notifies observers the given user is a new connection
42
-	 *
43
-	 * @param user The newly connected user
44
-	 */
45
-	private void notifyNewUserObservers(PeerUser user) {
46
-		if (newUsersObservers != null) {
47
-			newUsersObservers.onUserConnected(user);
48
-		}
49
-	}
50
-
51
-	/**
52 31
 	 * Discovers all active users over the network
53 32
 	 * and create a connection for each.
54
-	 * Observers are notified for each new successful connection.
55 33
 	 *
56 34
 	 * @param errorCallback The function to call on error
57
-	 * @see UserList#setNewUserObserver(UserConnectionCallback)
58 35
 	 */
59 36
 	public void discoverActiveUsers(ErrorCallback errorCallback) {
60 37
 		netDiscoverer.discoverActiveUsers("CLAVARDATOR_BROADCAST", (ipAddr, data) -> {
61 38
 			Log.v(this.getClass().getSimpleName(), "Discovered new user: " + data);
62
-			final PeerUser user = createNewPendingUser(data.trim());
63
-			if (user != null) {
64
-				user.createConnection(ipAddr, () -> onUserConnectionSuccess(user), errorCallback);
65
-			}
39
+			new PeerHandshake().createConnection(
40
+					ipAddr,
41
+					this::onUserConnectionSuccess,
42
+					errorCallback);
66 43
 		}, errorCallback);
67 44
 	}
68 45
 
@@ -94,56 +71,26 @@ public class UserList {
94 71
 				(clientSocket) -> {
95 72
 					Log.v(this.getClass().getSimpleName(),
96 73
 							"new connection from user at address: " + clientSocket.getInetAddress().toString());
97
-					final PeerUser user = createNewPendingUser();
98
-					if (user != null) {
99
-						user.acceptConnection(clientSocket, () ->
100
-								onUserConnectionSuccess(user), errorCallback);
101
-					}
74
+					new PeerHandshake().acceptConnection(clientSocket, this::onUserConnectionSuccess, errorCallback);
102 75
 				},
103 76
 				errorCallback);
104 77
 	}
105 78
 
106 79
 	/**
107 80
 	 * Notify observers and subscribe to user changes.
108
-	 *
109
-	 * @param user The newly connected user
110
-	 */
111
-	private void onUserConnectionSuccess(PeerUser user) {
112
-		notifyNewUserObservers(user);
113
-		user.addObserver(evt -> userChangeObserver(user, evt));
114
-	}
115
-
116
-	private PeerUser createNewPendingUser() {
117
-		PeerUser user = new PeerUser();
118
-		addUserToPendingList(user);
119
-		return user;
120
-	}
121
-
122
-	/**
123
-	 * Creates a new user from its id and puts it in the inactive user list.
124
-	 * We first try to fetch it from active and inactive users to prevent duplicates.
125
-	 * We do not notify observers yet as the connection may be canceled on username checks.
126
-	 *
127
-	 * @param id The new user's id.
128
-	 * @return A new PeerUser, or null if the user is already connected
129 81
 	 */
130
-	private PeerUser createNewPendingUser(String id) {
131
-		// If already connected, warn and return
132
-		if (activeUsers.containsKey(id)) {
133
-			Log.w(getClass().getSimpleName(),
134
-					"An already connected user tried to initiate a new connection: user id " + id);
135
-			return null;
82
+	private void onUserConnectionSuccess(PeerHandshake handshake) {
83
+		// Check if we already know this user
84
+		final UserInformation userInfo = handshake.getUserInformation();
85
+		final PeerUser savedUser = userHashmap.get(userInfo.id);
86
+		if (savedUser != null) {
87
+			savedUser.init(handshake.getConnection(), userInfo.id, userInfo.getUsername(), null);
88
+		} else {
89
+			final PeerUser user = new PeerUser();
90
+			user.init(handshake.getConnection(), userInfo.id, userInfo.getUsername(), null);
91
+			userHashmap.put(user.id, user);
92
+			Platform.runLater(() -> userObservableList.add(user));
136 93
 		}
137
-
138
-		// Get the user if already existing
139
-		PeerUser user = inactiveUsers.get(id);
140
-		// else create it
141
-		if (user == null) {
142
-			// Username is set on TCP connection start or db fetch
143
-			user = new PeerUser(id);
144
-			addUserToPendingList(user);
145
-		}
146
-		return user;
147 94
 	}
148 95
 
149 96
 	/**
@@ -156,75 +103,8 @@ public class UserList {
156 103
 	 */
157 104
 	private void createNewInactiveUser(String id, String username) {
158 105
 		final PeerUser user = new PeerUser(id, username);
159
-		inactiveUsers.put(id, user);
160
-		notifyNewUserObservers(user);
161
-	}
162
-
163
-	private void addUserToPendingList(PeerUser user) {
164
-		if (!pendingUsers.contains(user)) {
165
-			pendingUsers.add(user);
166
-		}
167
-	}
168
-
169
-	/**
170
-	 * Tries to move the given user from inactive list to active list
171
-	 *
172
-	 * @param user The user to move
173
-	 */
174
-	private void moveUserToActiveList(PeerUser user) {
175
-		final String id = user.id;
176
-		if (!inactiveUsers.containsKey(id)) {
177
-			if (activeUsers.containsKey(id)) {
178
-				Log.w(getClass().getSimpleName(), "Tried to set state " + PeerUser.State.CONNECTED + " on an already connected user: user id " + id);
179
-			} else {
180
-				Log.w(getClass().getSimpleName(), "Tried to set state " + PeerUser.State.CONNECTED + " on an unknown user: user id " + id);
181
-			}
182
-			return;
183
-		}
184
-		inactiveUsers.remove(user.id);
185
-		activeUsers.put(user.id, user);
186
-	}
187
-
188
-	/**
189
-	 * Tries to move the given user from active list to inactive list
190
-	 *
191
-	 * @param user The user to move
192
-	 */
193
-	private void moveUserToInactiveList(PeerUser user) {
194
-		final String id = user.id;
195
-		if (!activeUsers.containsKey(id)) {
196
-			if (inactiveUsers.containsKey(id)) {
197
-				Log.w(getClass().getSimpleName(), "Tried to set state " + PeerUser.State.DISCONNECTED + " on an already disconnected user: user id " + id);
198
-			} else {
199
-				Log.w(getClass().getSimpleName(), "Tried to set state " + PeerUser.State.DISCONNECTED + " on an unknown user: user id " + id);
200
-			}
201
-			return;
202
-		}
203
-		activeUsers.remove(id);
204
-		inactiveUsers.put(id, user);
205
-	}
206
-
207
-	/**
208
-	 * Move user from active or inactive list when its state changes.
209
-	 *
210
-	 * @param user The user which changed
211
-	 * @param evt  The change event
212
-	 */
213
-	private void userChangeObserver(PeerUser user, PropertyChangeEvent evt) {
214
-		if (evt.getPropertyName().equals("state")) {
215
-			PeerUser.State oldState = (PeerUser.State) evt.getOldValue();
216
-			PeerUser.State newState = (PeerUser.State) evt.getNewValue();
217
-
218
-			if ((oldState == PeerUser.State.DISCONNECTED ||
219
-					(oldState == PeerUser.State.CONNECTING) && newState == PeerUser.State.CONNECTED)) {
220
-				moveUserToActiveList(user);
221
-			} else if (oldState == PeerUser.State.CONNECTED &&
222
-					(newState == PeerUser.State.DISCONNECTED || newState == PeerUser.State.CONNECTING)) {
223
-				moveUserToInactiveList(user);
224
-			}
225
-			Log.v(getClass().getSimpleName(), "State of user " + user.id + " updated from " +
226
-					oldState.toString() + " to " + newState.toString());
227
-		}
106
+		userHashmap.put(id, user);
107
+		Platform.runLater(() -> userObservableList.add(user));
228 108
 	}
229 109
 
230 110
 	/**
@@ -235,17 +115,14 @@ public class UserList {
235 115
 	 */
236 116
 	public boolean isUsernameAvailable(String username) {
237 117
 		Predicate<User> usernameEqual = user -> user.getUsername() != null && user.getUsername().equals(username);
238
-		return activeUsers.values().stream().noneMatch(usernameEqual) &&
239
-				inactiveUsers.values().stream().noneMatch(usernameEqual);
118
+		return userHashmap.values().stream().noneMatch(usernameEqual);
240 119
 	}
241 120
 
242 121
 	/**
243 122
 	 * Tells all active users that our username changed
244 123
 	 */
245 124
 	public void propagateUsernameChange() {
246
-		activeUsers.forEach((id, user) -> {
247
-			user.sendCurrentUser(Throwable::printStackTrace);
248
-		});
125
+		userHashmap.forEach((id, user) -> user.sendCurrentUser(Throwable::printStackTrace));
249 126
 	}
250 127
 
251 128
 	/**
@@ -256,13 +133,13 @@ public class UserList {
256 133
 		netDiscoverer.stopDiscovery();
257 134
 		connectionListener.stopAccepting();
258 135
 		db.close();
259
-		for (PeerUser user : activeUsers.values()) {
136
+		for (PeerUser user : userHashmap.values()) {
260 137
 			user.disconnect();
261 138
 		}
262 139
 	}
263 140
 
264
-	public interface UserConnectionCallback {
265
-		void onUserConnected(PeerUser user);
141
+	public ObservableList<PeerUser> getUserObservableList() {
142
+		return userObservableList;
266 143
 	}
267 144
 
268 145
 	public interface UserListLoadedCallback {

Loading…
Cancel
Save