add username change and check for duplicates
This commit is contained in:
parent
060137115f
commit
030e9b3b0a
8 changed files with 194 additions and 77 deletions
|
@ -0,0 +1,9 @@
|
|||
package fr.insa.clavardator.errors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class UsernameTakenException extends Exception implements Serializable {
|
||||
public UsernameTakenException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -19,6 +19,8 @@ import java.io.IOException;
|
|||
import java.net.SocketException;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class MainController implements Initializable {
|
||||
|
||||
|
@ -58,8 +60,13 @@ public class MainController implements Initializable {
|
|||
private void onCurrentUserStateChange(CurrentUser.State newState) {
|
||||
if (newState == CurrentUser.State.VALID)
|
||||
startChat();
|
||||
else
|
||||
Platform.runLater(() -> showLogin(newState == CurrentUser.State.INVALID));
|
||||
else {
|
||||
final boolean isError = newState == CurrentUser.State.INVALID;
|
||||
if (isError) {
|
||||
endChat();
|
||||
}
|
||||
Platform.runLater(() -> showLogin(isError));
|
||||
}
|
||||
}
|
||||
|
||||
private void openEditUsernameDialog(EditUsernameDialogController.Mode mode) {
|
||||
|
@ -72,11 +79,11 @@ public class MainController implements Initializable {
|
|||
}
|
||||
});
|
||||
editUserDialogController.setOnSuccessListener(() -> {
|
||||
if (!initialMode) {
|
||||
if (mode == EditUsernameDialogController.Mode.EDIT) {
|
||||
showSnackbarEvent("Nom d'utilisateur changé !", SnackbarController.Mode.SUCCESS);
|
||||
}
|
||||
});
|
||||
editUserDialogController.show(root, mode);
|
||||
Platform.runLater(() -> editUserDialogController.show(root, mode));
|
||||
}
|
||||
|
||||
private void showSnackbarEvent(String text, SnackbarController.Mode mode) {
|
||||
|
@ -99,15 +106,26 @@ public class MainController implements Initializable {
|
|||
|
||||
private void discoverActiveUsers() {
|
||||
if (userList != null) {
|
||||
userList.discoverActiveUsers((e) -> CurrentUser.getInstance().setState(CurrentUser.State.INVALID));
|
||||
userList.discoverActiveUsers((e) -> {
|
||||
Log.e(this.getClass().getSimpleName(), "Error discovering users", e);
|
||||
CurrentUser.getInstance().setState(CurrentUser.State.INVALID);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void startListening() {
|
||||
if (userList != null) {
|
||||
userList.startDiscoveryListening();
|
||||
userList.startUserListening((e) ->
|
||||
Log.e(this.getClass().getSimpleName(), "Error listening to users", e));
|
||||
userList.startUserListening((e) -> {
|
||||
Log.e(this.getClass().getSimpleName(), "Error listening to users", e);
|
||||
CurrentUser.getInstance().setState(CurrentUser.State.INVALID);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void stopListening() {
|
||||
if (userList != null) {
|
||||
userList.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,6 +136,11 @@ public class MainController implements Initializable {
|
|||
Platform.runLater(this::showChat);
|
||||
}
|
||||
|
||||
private void endChat() {
|
||||
Log.v(this.getClass().getSimpleName(), "Chat ended");
|
||||
stopListening();
|
||||
}
|
||||
|
||||
private void showChat() {
|
||||
Log.v(this.getClass().getSimpleName(), "Chat shown");
|
||||
loadingController.hide();
|
||||
|
@ -128,7 +151,14 @@ public class MainController implements Initializable {
|
|||
Log.v(this.getClass().getSimpleName(), "Login shown");
|
||||
mainContainer.setVisible(false);
|
||||
loadingController.hide();
|
||||
Timer t = new Timer();
|
||||
t.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
openEditUsernameDialog(isError ? EditUsernameDialogController.Mode.ERROR : EditUsernameDialogController.Mode.INITIAL);
|
||||
t.cancel();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private void showError() {
|
||||
|
@ -162,5 +192,6 @@ public class MainController implements Initializable {
|
|||
this.userList = userList;
|
||||
listController.setUserList(userList);
|
||||
listController.setRefreshUserListener(this::discoverActiveUsers);
|
||||
editUserDialogController.setUserList(userList);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.jfoenix.controls.JFXTextField;
|
|||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import fr.insa.clavardator.ui.ButtonPressEvent;
|
||||
import fr.insa.clavardator.users.CurrentUser;
|
||||
import fr.insa.clavardator.users.UserList;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
|
@ -16,8 +17,6 @@ import javafx.scene.layout.StackPane;
|
|||
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
public class EditUsernameDialogController implements Initializable {
|
||||
|
||||
|
@ -44,21 +43,16 @@ public class EditUsernameDialogController implements Initializable {
|
|||
private Validator validator;
|
||||
private ButtonPressEvent successListener;
|
||||
private ButtonPressEvent cancelListener;
|
||||
private UserList userList;
|
||||
|
||||
@FXML
|
||||
private void onConfirm() {
|
||||
setLocked(true);
|
||||
CurrentUser.getInstance().setUsername(textField.getText());
|
||||
// TODO replace by network call
|
||||
Timer t = new Timer();
|
||||
t.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
// onError();
|
||||
if (userList != null && userList.isUsernameAvailable(textField.getText())) {
|
||||
onSuccess();
|
||||
t.cancel();
|
||||
} else {
|
||||
onError();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
@ -69,6 +63,10 @@ public class EditUsernameDialogController implements Initializable {
|
|||
hide();
|
||||
}
|
||||
|
||||
public void setUserList(UserList userList) {
|
||||
this.userList = userList;
|
||||
}
|
||||
|
||||
public void setOnSuccessListener(ButtonPressEvent listener) {
|
||||
this.successListener = listener;
|
||||
}
|
||||
|
@ -149,9 +147,14 @@ public class EditUsernameDialogController implements Initializable {
|
|||
setFieldError(State.VALID);
|
||||
setLocked(false);
|
||||
hide();
|
||||
final String newName = textField.getText();
|
||||
CurrentUser.getInstance().setUsername(newName);
|
||||
if (successListener != null) {
|
||||
successListener.onPress();
|
||||
}
|
||||
if (userList != null) {
|
||||
userList.propagateUsernameChange();
|
||||
}
|
||||
}
|
||||
|
||||
private void onError() {
|
||||
|
|
|
@ -55,8 +55,12 @@ public class UserListController implements Initializable {
|
|||
}
|
||||
|
||||
private void onUserConnected(PeerUser user) {
|
||||
if (!peerUserListView.getItems().contains(user)) {
|
||||
Log.v(this.getClass().getSimpleName(), "Add user to UI");
|
||||
Platform.runLater(() -> peerUserListView.getItems().add(user));
|
||||
} else {
|
||||
Log.w(this.getClass().getSimpleName(), "User already added to ui, skipping...");
|
||||
}
|
||||
}
|
||||
|
||||
public void setUserList(UserList userList) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package fr.insa.clavardator.users;
|
|||
|
||||
import fr.insa.clavardator.chat.ChatHistory;
|
||||
import fr.insa.clavardator.chat.Message;
|
||||
import fr.insa.clavardator.errors.UsernameTakenException;
|
||||
import fr.insa.clavardator.network.PeerConnection;
|
||||
import fr.insa.clavardator.util.ErrorCallback;
|
||||
import fr.insa.clavardator.util.Log;
|
||||
|
@ -35,16 +36,14 @@ public class PeerUser extends User implements Comparable<PeerUser> {
|
|||
* @param callback The function to call on success
|
||||
* @param errorCallback The function to call on socket error
|
||||
*/
|
||||
public void connect(InetAddress ipAddr, UserConnectedCallback callback, ErrorCallback errorCallback) {
|
||||
if (connection != null && connection.isOpen()) {
|
||||
connection.close();
|
||||
}
|
||||
public void createConnection(InetAddress ipAddr, UserConnectedCallback callback, ErrorCallback errorCallback) {
|
||||
closeConnection();
|
||||
Log.v(this.getClass().getSimpleName(), "Creating new TCP connection with " + id);
|
||||
// Connect to the peer
|
||||
setState(State.CONNECTING);
|
||||
connection = new PeerConnection(
|
||||
ipAddr,
|
||||
(thisConnection) -> init(thisConnection, callback, errorCallback),
|
||||
(thisConnection) -> init(thisConnection, false, callback, errorCallback),
|
||||
e -> {
|
||||
Log.e(this.getClass().getSimpleName(), "Could not create TCP connection with " + id, e);
|
||||
disconnect();
|
||||
|
@ -59,16 +58,19 @@ public class PeerUser extends User implements Comparable<PeerUser> {
|
|||
* @param callback The function to call on success, with the new ActiveUser as parameter
|
||||
* @param errorCallback The function to call on socket error
|
||||
*/
|
||||
public void connect(Socket socket, UserConnectedCallback callback, ErrorCallback errorCallback) {
|
||||
if (connection != null && connection.isOpen()) {
|
||||
connection.close();
|
||||
}
|
||||
public void acceptConnection(Socket socket, UserConnectedCallback callback, ErrorCallback errorCallback) {
|
||||
closeConnection();
|
||||
setState(State.CONNECTING);
|
||||
connection = new PeerConnection(socket);
|
||||
|
||||
init(connection, callback, errorCallback);
|
||||
init(connection, true, callback, errorCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a basic text message to this user
|
||||
*
|
||||
* @param msg The text message to send
|
||||
* @param errorCallback Callback on error
|
||||
*/
|
||||
public void sendTextMessage(String msg, @Nullable ErrorCallback errorCallback) {
|
||||
if (connection != null) {
|
||||
Log.v(this.getClass().getSimpleName(),
|
||||
|
@ -83,38 +85,81 @@ public class PeerUser extends User implements Comparable<PeerUser> {
|
|||
}
|
||||
}
|
||||
|
||||
private void init(PeerConnection thisConnection, UserConnectedCallback callback, ErrorCallback errorCallback) {
|
||||
/**
|
||||
* Sends current user information to this user
|
||||
*
|
||||
* @param errorCallback Callback on error
|
||||
*/
|
||||
public void sendCurrentUser(@Nullable ErrorCallback errorCallback) {
|
||||
if (connection != null) {
|
||||
final String username = CurrentUser.getInstance().getUsername();
|
||||
Log.v(this.getClass().getSimpleName(),
|
||||
"Sending current user information to " + this.getUsername() + " / " + this.getId() + ": " + username);
|
||||
connection.send(
|
||||
new UserInformation(CurrentUser.getInstance()),
|
||||
null,
|
||||
errorCallback);
|
||||
} else {
|
||||
Log.e(this.getClass().getSimpleName(), "Could not send new username: connection is not initialized");
|
||||
}
|
||||
}
|
||||
|
||||
private void sendUsernameTaken(PeerConnection thisConnection) {
|
||||
Log.v(this.getClass().getSimpleName(), "Received username request using current username");
|
||||
thisConnection.send(new UsernameTakenException("Username taken"), this::disconnect, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the connection with this user.
|
||||
* Both user exchange their information
|
||||
*
|
||||
* @param thisConnection The peer connection to use
|
||||
* @param callback Callback on success
|
||||
* @param errorCallback Callback on error
|
||||
*/
|
||||
private void init(PeerConnection thisConnection, boolean isReceiving, UserConnectedCallback callback, ErrorCallback errorCallback) {
|
||||
// Send our username
|
||||
String currentUserUsername = CurrentUser.getInstance().getUsername();
|
||||
int currentUserId = CurrentUser.getInstance().getId();
|
||||
thisConnection.send(new UserInformation(currentUserId, currentUserUsername), null, e -> {
|
||||
thisConnection.send(new UserInformation(CurrentUser.getInstance()), null, e -> {
|
||||
disconnect();
|
||||
errorCallback.onError(e);
|
||||
});
|
||||
|
||||
// Receive peer's username
|
||||
thisConnection.receiveOne(msg -> {
|
||||
assert msg instanceof UserInformation;
|
||||
if (msg instanceof UserInformation) {
|
||||
UserInformation userInfo = (UserInformation) msg;
|
||||
// TODO : Check username unique
|
||||
assert id == userInfo.id;
|
||||
setUsername(userInfo.getUsername());
|
||||
final String receivedUsername = userInfo.getUsername();
|
||||
if (!receivedUsername.equals(CurrentUser.getInstance().getUsername())) {
|
||||
setUsername(receivedUsername);
|
||||
callback.onUserConnected();
|
||||
|
||||
// Subscribe to incoming messages
|
||||
subscribeToMessages(e -> {
|
||||
disconnect();
|
||||
errorCallback.onError(e);
|
||||
});
|
||||
setState(State.CONNECTED);
|
||||
|
||||
} else if (isReceiving) {
|
||||
sendUsernameTaken(thisConnection);
|
||||
} else {
|
||||
disconnect();
|
||||
errorCallback.onError(new Exception("Tried to use same username as remote"));
|
||||
}
|
||||
} else {
|
||||
disconnect();
|
||||
errorCallback.onError(new Exception("Did not receive remote username"));
|
||||
}
|
||||
}, e -> {
|
||||
disconnect();
|
||||
errorCallback.onError(e);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Subscribe to this user messages.
|
||||
* If receiving new user info, update this user.
|
||||
* If receiving text message, store it in the history.
|
||||
*
|
||||
* @param errorCallback Callback on error
|
||||
*/
|
||||
private void subscribeToMessages(ErrorCallback errorCallback) {
|
||||
connection.receive(
|
||||
msg -> {
|
||||
|
@ -139,19 +184,28 @@ public class PeerUser extends User implements Comparable<PeerUser> {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Close the connection and set state to disconnected
|
||||
*/
|
||||
public void disconnect() {
|
||||
Log.v(this.getClass().getSimpleName(), "Disconnecting from user: " + id);
|
||||
closeConnection();
|
||||
setState(State.DISCONNECTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection to this user
|
||||
*/
|
||||
private void closeConnection() {
|
||||
if (connection != null && connection.isOpen()) {
|
||||
connection.close();
|
||||
connection = null;
|
||||
}
|
||||
setState(State.DISCONNECTED);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the value of history
|
||||
* Gets the value of history
|
||||
*
|
||||
* @return the value of history
|
||||
*/
|
||||
|
@ -159,11 +213,21 @@ public class PeerUser extends User implements Comparable<PeerUser> {
|
|||
return history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this user state
|
||||
*
|
||||
* @param state The new state
|
||||
*/
|
||||
private void setState(State state) {
|
||||
pcs.firePropertyChange("state", this.state, state);
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this user is active.
|
||||
*
|
||||
* @return True id active, false otherwise
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return state == State.CONNECTED;
|
||||
}
|
||||
|
@ -178,12 +242,18 @@ public class PeerUser extends User implements Comparable<PeerUser> {
|
|||
return getUsername().compareTo(peerUser.getUsername());
|
||||
}
|
||||
|
||||
/**
|
||||
* The user connection state
|
||||
*/
|
||||
public enum State {
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
DISCONNECTED,
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when this user successfully connects
|
||||
*/
|
||||
public interface UserConnectedCallback {
|
||||
void onUserConnected();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package fr.insa.clavardator.users;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.io.Serializable;
|
||||
|
|
|
@ -2,6 +2,9 @@ package fr.insa.clavardator.users;
|
|||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Class used to serialize useful user information
|
||||
*/
|
||||
public class UserInformation implements Serializable {
|
||||
public final int id;
|
||||
private final String username;
|
||||
|
|
|
@ -38,10 +38,9 @@ public class UserList {
|
|||
netDiscoverer.discoverActiveUsers("CLAVARDATOR_BROADCAST", (ipAddr, data) -> {
|
||||
int id = getIdFromIp(ipAddr);
|
||||
Log.v(this.getClass().getSimpleName(), "Discovered new user: " + id);
|
||||
final PeerUser finalUser = createNewUser(id);
|
||||
if (finalUser != null) {
|
||||
finalUser.connect(ipAddr, () ->
|
||||
finalUser.addObserver(evt -> userChangeObserver(finalUser, evt)), errorCallback);
|
||||
final PeerUser user = createNewUser(id);
|
||||
if (user != null) {
|
||||
user.createConnection(ipAddr, () -> onUserConnectionSuccess(user), errorCallback);
|
||||
}
|
||||
}, errorCallback);
|
||||
}
|
||||
|
@ -56,17 +55,22 @@ public class UserList {
|
|||
public void startUserListening(ErrorCallback errorCallback) {
|
||||
connectionListener.acceptConnection(
|
||||
(clientSocket) -> {
|
||||
int id = getIdFromIp(clientSocket.getInetAddress());
|
||||
final int id = getIdFromIp(clientSocket.getInetAddress());
|
||||
Log.v(this.getClass().getSimpleName(), "new connection from user: " + id);
|
||||
final PeerUser finalUser = createNewUser(id);
|
||||
if (finalUser != null) {
|
||||
finalUser.connect(clientSocket, () ->
|
||||
finalUser.addObserver(evt -> userChangeObserver(finalUser, evt)), errorCallback);
|
||||
final PeerUser user = createNewUser(id);
|
||||
if (user != null) {
|
||||
user.acceptConnection(clientSocket, () ->
|
||||
onUserConnectionSuccess(user), errorCallback);
|
||||
}
|
||||
},
|
||||
errorCallback);
|
||||
}
|
||||
|
||||
private void onUserConnectionSuccess(PeerUser user) {
|
||||
notifyNewUserObservers(user);
|
||||
user.addObserver(evt -> userChangeObserver(user, evt));
|
||||
}
|
||||
|
||||
private PeerUser createNewUser(int id) {
|
||||
// If already connected, warn and return
|
||||
if (activeUsers.containsKey(id)) {
|
||||
|
@ -82,7 +86,6 @@ public class UserList {
|
|||
// Username is set on TCP connection start
|
||||
user = new PeerUser(id);
|
||||
inactiveUsers.put(id, user);
|
||||
notifyNewUserObservers(user);
|
||||
}
|
||||
|
||||
return user;
|
||||
|
@ -146,18 +149,18 @@ public class UserList {
|
|||
* @return True if the username is available
|
||||
*/
|
||||
public boolean isUsernameAvailable(String username) {
|
||||
Predicate<User> usernameEqual = user -> user.getUsername().equals(username);
|
||||
Predicate<User> usernameEqual = user -> user.getUsername() != null && user.getUsername().equals(username);
|
||||
return activeUsers.values().stream().noneMatch(usernameEqual) &&
|
||||
inactiveUsers.values().stream().noneMatch(usernameEqual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell all active users that our username changed
|
||||
*
|
||||
* @param username The new username
|
||||
*/
|
||||
public void propagateUsernameChange(String username) {
|
||||
// TODO
|
||||
public void propagateUsernameChange() {
|
||||
activeUsers.forEach((id, user) -> {
|
||||
user.sendCurrentUser(Throwable::printStackTrace);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -177,8 +180,4 @@ public class UserList {
|
|||
void onUserConnected(PeerUser user);
|
||||
}
|
||||
|
||||
public interface UserDisconnectionCallback {
|
||||
void onUserDisconnected(PeerUser user);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue