|
@@ -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 {
|