use unique ID instead of ip

This commit is contained in:
Arnaud Vergnet 2021-01-05 22:00:30 +01:00
parent 1ab27f4f87
commit fcb678f4fe
12 changed files with 208 additions and 186 deletions

View file

@ -48,7 +48,7 @@ public class Message implements Serializable {
} }
public UserInformation getCorrespondent() { public UserInformation getCorrespondent() {
if (CurrentUser.getInstance().isLocalId(sender.id)) { if (CurrentUser.getInstance().getId() != null && CurrentUser.getInstance().getId().equals(sender.id)) {
return recipient; return recipient;
} else { } else {
return sender; return sender;

View file

@ -81,8 +81,8 @@ public class DatabaseController {
@Language("SQL") String sql = "CREATE TABLE IF NOT EXISTS message " + @Language("SQL") String sql = "CREATE TABLE IF NOT EXISTS message " +
"(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + "(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
" timestamp DATETIME NOT NULL, " + " timestamp DATETIME NOT NULL, " +
" sender INTEGER UNSIGNED NOT NULL, " + " sender TEXT NOT NULL, " +
" recipient INTEGER UNSIGNED NOT NULL, " + " recipient TEXT NOT NULL, " +
" text TEXT, " + " text TEXT, " +
" file_path TEXT)"; " file_path TEXT)";
@ -99,7 +99,7 @@ public class DatabaseController {
*/ */
private void createUserTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) { private void createUserTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
@Language("SQL") String sql = "CREATE TABLE IF NOT EXISTS user " + @Language("SQL") String sql = "CREATE TABLE IF NOT EXISTS user " +
"(id INTEGER UNSIGNED PRIMARY KEY NOT NULL," + "(id TEXT PRIMARY KEY NOT NULL," +
" username TINYTEXT NULLABLE )"; " username TINYTEXT NULLABLE )";
Log.v(getClass().getSimpleName(), "Creating table user..."); Log.v(getClass().getSimpleName(), "Creating table user...");
@ -107,6 +107,22 @@ public class DatabaseController {
executor.start(); executor.start();
} }
/**
* Creates the table used to store users
*
* @param callback The function to call on success
* @param errorCallback The function to call on error
*/
private void createCurrentUserTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
@Language("SQL") String sql = "CREATE TABLE IF NOT EXISTS current_user " +
"(id TEXT PRIMARY KEY NOT NULL," +
" username TINYTEXT NULLABLE )";
Log.v(getClass().getSimpleName(), "Creating table current user...");
UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
executor.start();
}
/** /**
* Creates all needed tables if non-existent * Creates all needed tables if non-existent
* *
@ -114,7 +130,7 @@ public class DatabaseController {
* @param errorCallback The function to call on error * @param errorCallback The function to call on error
*/ */
public void initTables(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) { public void initTables(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
createMessageTable(() -> createUserTable(callback, errorCallback), errorCallback); createMessageTable(() -> createCurrentUserTable(() -> createUserTable(callback, errorCallback), errorCallback), errorCallback);
} }
/** /**
@ -143,6 +159,13 @@ public class DatabaseController {
executor.start(); executor.start();
} }
private void dropCurrentUserTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
@Language("SQL") String sql = "DROP TABLE IF EXISTS current_user";
Log.v(getClass().getSimpleName(), "Dropping table current_user...");
UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
executor.start();
}
/** /**
* Destroys all tables * Destroys all tables
* *
@ -150,7 +173,7 @@ public class DatabaseController {
* @param errorCallback The function to call on error * @param errorCallback The function to call on error
*/ */
private void dropTables(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) { private void dropTables(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
dropMessageTable(() -> dropUserTable(callback, errorCallback), errorCallback); dropMessageTable(() -> dropUserTable(() -> dropCurrentUserTable(callback, errorCallback), errorCallback), errorCallback);
} }
/** /**
@ -171,14 +194,14 @@ public class DatabaseController {
* @param errorCallback The function to call on error * @param errorCallback The function to call on error
*/ */
public void getAllUsers(UsersCallback callback, ErrorCallback errorCallback) { public void getAllUsers(UsersCallback callback, ErrorCallback errorCallback) {
@Language("SQL") String sql = "SELECT * FROM user WHERE id != " + CurrentUser.getInstance().getId(); @Language("SQL") String sql = "SELECT * FROM user";
Log.v(getClass().getSimpleName(), "Fetching users from db... "); Log.v(getClass().getSimpleName(), "Fetching users from db... ");
QueryExecutor executor = new QueryExecutor(sql, res -> { QueryExecutor executor = new QueryExecutor(sql, res -> {
ArrayList<User> userList = new ArrayList<>(); ArrayList<User> userList = new ArrayList<>();
while (res.next()) { while (res.next()) {
int id = res.getInt("id"); String id = res.getString("id");
String username = res.getString("username"); String username = res.getString("username");
userList.add(new User(id, username)); userList.add(new User(id, username));
} }
@ -189,6 +212,26 @@ public class DatabaseController {
executor.start(); executor.start();
} }
public void getCurrentUser(CurrentUserCallback callback, ErrorCallback errorCallback) {
@Language("SQL") String sql = "SELECT * FROM current_user ";
Log.v(getClass().getSimpleName(), "Fetching current_user from db... ");
QueryExecutor executor = new QueryExecutor(sql, res -> {
UserInformation user = null;
int nbRows = 0;
if (res.next()) {
String id = res.getString("id");
String username = res.getString("username");
user = new UserInformation(id, username);
nbRows = 1;
}
Log.v(getClass().getSimpleName(), nbRows + " users fetched");
callback.onFetched(user);
}, errorCallback);
executor.start();
}
/** /**
* Adds a message to the database for this user. * Adds a message to the database for this user.
@ -200,9 +243,8 @@ public class DatabaseController {
*/ */
public void addMessage(Message message, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) { public void addMessage(Message message, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
// Insert the correspondent if not already in the database // Insert the correspondent if not already in the database
Log.v(getClass().getSimpleName(), "Inserting correpondent into db... "); Log.v(getClass().getSimpleName(), "Inserting correspondent into db... ");
addUser(message.getCorrespondent(), () -> { addUser(message.getCorrespondent(), () -> {
// Handle messages containing a file // Handle messages containing a file
String filePath = "NULL"; String filePath = "NULL";
// TODO: Handle messages containing files: // TODO: Handle messages containing files:
@ -212,24 +254,16 @@ public class DatabaseController {
// filePath = ((FileMessage) message).getFileName(); // filePath = ((FileMessage) message).getFileName();
} }
// TODO: do this in the construction of the message instead? String recipientId = message.getRecipient().id;
int recipientId; String senderId = message.getSender().id;
int senderId;
// if (CurrentUser.getInstance().isLocalId(message.getRecipient().id)) {
// recipientId = CurrentUser.getInstance().getId();
senderId = message.getSender().id;
// } else {
// senderId = CurrentUser.getInstance().getId();
recipientId = message.getRecipient().id;
// }
// Insert the new message // Insert the new message
@Language("SQL") String sql = "INSERT INTO message " + @Language("SQL") String sql = "INSERT INTO message " +
"(timestamp, sender, recipient, text, file_path) " + "(timestamp, sender, recipient, text, file_path) " +
"VALUES (" + "VALUES (" +
message.getDate().getTime() + ", " + message.getDate().getTime() + ", " +
senderId + ", " + "'" + senderId + "', " +
recipientId + ", " + "'" + recipientId + "', " +
"\"" + message.getText() + "\", " + "\"" + message.getText() + "\", " +
filePath + filePath +
")"; ")";
@ -251,10 +285,10 @@ public class DatabaseController {
@Language("SQL") String sql; @Language("SQL") String sql;
if (user.getUsername() != null) { if (user.getUsername() != null) {
sql = "INSERT OR IGNORE INTO user (id, username) " + sql = "INSERT OR IGNORE INTO user (id, username) " +
"VALUES (" + user.id + ", \"" + user.getUsername() + "\")"; "VALUES ('" + user.id + "', '" + user.getUsername() + "')";
} else { } else {
sql = "INSERT OR IGNORE INTO user (id) " + sql = "INSERT OR IGNORE INTO user (id) " +
"VALUES (" + user.id + ")"; "VALUES ('" + user.id + "')";
} }
Log.v(getClass().getSimpleName(), "Adding user to db: " + user.id + " / " + user.getUsername()); Log.v(getClass().getSimpleName(), "Adding user to db: " + user.id + " / " + user.getUsername());
@ -262,6 +296,21 @@ public class DatabaseController {
executor.start(); executor.start();
} }
public void addCurrentUser(UserInformation user, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
@Language("SQL") String sql;
if (user.getUsername() != null) {
sql = "INSERT OR IGNORE INTO current_user (id, username) " +
"VALUES ('" + user.id + "', '" + user.getUsername() + "')";
} else {
sql = "INSERT OR IGNORE INTO current_user (id) " +
"VALUES ('" + user.id + "')";
}
Log.v(getClass().getSimpleName(), "Adding current_user to db: " + user.id + " / " + user.getUsername());
UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
executor.start();
}
/** /**
* Gets the chat history for a given time frame * Gets the chat history for a given time frame
* *
@ -272,22 +321,24 @@ public class DatabaseController {
* @param errorCallback The function to call on error * @param errorCallback The function to call on error
*/ */
public void getChatHistory(UserInformation user, Date from, Date to, HistoryCallback callback, ErrorCallback errorCallback) { public void getChatHistory(UserInformation user, Date from, Date to, HistoryCallback callback, ErrorCallback errorCallback) {
// TODO update to search in current_user table as well
@Language("SQL") String sql = @Language("SQL") String sql =
"SELECT timestamp, s.id AS sender_id, s.username AS sender_username, " + "SELECT timestamp, s.id AS sender_id, s.username AS sender_username, " +
"r.id AS recipient_id, r.username AS recipient_username, text, file_path " + "r.id AS recipient_id, r.username AS recipient_username, text, file_path " +
"FROM message JOIN user AS s ON sender = s.id " + "FROM message JOIN user AS s ON sender = s.id " +
" JOIN user AS r ON recipient = r.id " + " JOIN user AS r ON recipient = r.id " +
"WHERE (s.id = " + user.id + " OR r.id = " + user.id + ") AND " + "WHERE (s.id = '" + user.id + "' OR r.id = '" + user.id + "') AND " +
"timestamp > " + from.getTime() + " AND timestamp < " + to.getTime() + " " + "timestamp > " + from.getTime() + " AND timestamp < " + to.getTime() + " " +
"ORDER BY timestamp"; "ORDER BY timestamp";
System.out.println(sql);
Log.v(getClass().getSimpleName(), "Fetching chat history from db... "); Log.v(getClass().getSimpleName(), "Fetching chat history from db... ");
QueryExecutor executor = new QueryExecutor(sql, res -> { QueryExecutor executor = new QueryExecutor(sql, res -> {
ArrayList<Message> chatHistory = new ArrayList<>(); ArrayList<Message> chatHistory = new ArrayList<>();
while (res.next()) { while (res.next()) {
int sId = res.getInt("sender_id"); String sId = res.getString("sender_id");
String sUsername = res.getString("sender_username"); String sUsername = res.getString("sender_username");
int rId = res.getInt("recipient_id"); String rId = res.getString("recipient_id");
String rUsername = res.getString("recipient_username"); String rUsername = res.getString("recipient_username");
Date date = new Date(res.getTimestamp("timestamp").getTime()); Date date = new Date(res.getTimestamp("timestamp").getTime());
String text = res.getString("text"); String text = res.getString("text");
@ -307,25 +358,14 @@ public class DatabaseController {
} }
public void updateUsername(UserInformation user, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) { public void updateUsername(UserInformation user, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
@Language("SQL") String sql = "UPDATE user SET username = '" + user.getUsername() + "' where id = " + user.id; @Language("SQL") String sql = "UPDATE user SET username = '" + user.getUsername() + "' where id = '" + user.id + "'";
UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback); UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
executor.start(); executor.start();
} }
public void getUsername(int id, UsernameCallback callback, ErrorCallback errorCallback) { public void updateCurrentUsername(UserInformation user, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
@Language("SQL") String sql = "SELECT username from user where id =" + id; @Language("SQL") String sql = "UPDATE current_user SET username = '" + user.getUsername() + "' where id = '" + user.id + "'";
UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
Log.v(getClass().getSimpleName(), "Fetching username from db... ");
QueryExecutor executor = new QueryExecutor(sql, res -> {
String username = null;
int nbRows = 0;
if (res.next()) {
username = res.getString("username");
nbRows = 1;
}
Log.v(getClass().getSimpleName(), nbRows + " username fetched");
callback.onUsernameFetched(username);
}, errorCallback);
executor.start(); executor.start();
} }
@ -404,6 +444,10 @@ public class DatabaseController {
void onUsersFetched(ArrayList<User> users); void onUsersFetched(ArrayList<User> users);
} }
public interface CurrentUserCallback {
void onFetched(UserInformation user);
}
public interface UsernameCallback { public interface UsernameCallback {
void onUsernameFetched(String username); void onUsernameFetched(String username);
} }

View file

@ -59,13 +59,4 @@ public class NetUtil {
final List<InetAddress> list = listAllLocalAddresses(); final List<InetAddress> list = listAllLocalAddresses();
return list.stream().anyMatch((a) -> Arrays.equals(address.getAddress(), a.getAddress())); return list.stream().anyMatch((a) -> Arrays.equals(address.getAddress(), a.getAddress()));
} }
public static int getIdFromIp(InetAddress ipAddr) {
byte[] addr = ipAddr.getAddress();
int id = 0;
for (byte b : addr) {
id = (id << 8) + b;
}
return id;
}
} }

View file

@ -46,11 +46,9 @@ public class MainController implements Initializable {
private JFXSnackbar snackbar; private JFXSnackbar snackbar;
private UserList userList; private UserList userList;
private boolean historyLoaded;
private boolean online; private boolean online;
public MainController() { public MainController() {
historyLoaded = false;
online = false; online = false;
currentUser = CurrentUser.getInstance(); currentUser = CurrentUser.getInstance();
currentUser.addObserver(propertyChangeEvent -> { currentUser.addObserver(propertyChangeEvent -> {
@ -65,13 +63,10 @@ public class MainController implements Initializable {
* If the current user becomes valid, start the chat. * If the current user becomes valid, start the chat.
* If it is invalid or not set, show the login screen. * If it is invalid or not set, show the login screen.
* <p> * <p>
* If the history is not yet loaded or the user is not initialized, do nothing.
* *
* @param newState The new user state * @param newState The new user state
*/ */
private void onCurrentUserStateChange(CurrentUser.State newState) { private void onCurrentUserStateChange(CurrentUser.State newState) {
// Only do an action if the history is loaded
if (historyLoaded) {
if (newState == CurrentUser.State.VALID) if (newState == CurrentUser.State.VALID)
startChat(); startChat();
else if (newState != CurrentUser.State.UNINITIALIZED) { else if (newState != CurrentUser.State.UNINITIALIZED) {
@ -82,18 +77,6 @@ public class MainController implements Initializable {
Platform.runLater(() -> showLogin(isError)); Platform.runLater(() -> showLogin(isError));
} }
} }
}
/**
* If the history loaded after the current user, fire the state change event again
*/
public void onHistoryLoaded() {
historyLoaded = true;
final CurrentUser.State userState = CurrentUser.getInstance().getState();
if (userState != CurrentUser.State.UNINITIALIZED) {
onCurrentUserStateChange(userState);
}
}
/** /**
* Opens a dialog allowing the user to set its username. * Opens a dialog allowing the user to set its username.
@ -272,10 +255,10 @@ public class MainController implements Initializable {
private void initDb() { private void initDb() {
final DatabaseController db = new DatabaseController(); final DatabaseController db = new DatabaseController();
db.initTables(() -> { db.initTables(() -> {
Log.v("INIT", "Current user: " + currentUser.getId()); userList.retrievedPreviousUsers(
currentUser.init(() -> { () -> currentUser.init(this::onInitError),
userList.retrievedPreviousUsers(this::onHistoryLoaded, this::onInitError); this::onInitError
}, this::onInitError); );
}, this::onInitError); }, this::onInitError);
} }

View file

@ -1,6 +1,7 @@
package fr.insa.clavardator.ui.chat; package fr.insa.clavardator.ui.chat;
import fr.insa.clavardator.chat.Message; import fr.insa.clavardator.chat.Message;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.ListCell; import javafx.scene.control.ListCell;
@ -29,8 +30,10 @@ public class MessageListItemCell extends ListCell<Message> {
if (item == null || empty) { if (item == null || empty) {
setGraphic(null); setGraphic(null);
} else { } else {
Platform.runLater(() -> {
setGraphic(view); setGraphic(view);
messageListItemController.setMessage(item); messageListItemController.setMessage(item);
});
} }
} }
} }

View file

@ -35,7 +35,7 @@ public class MessageListItemController implements Initializable {
button.setText(message.getText()); button.setText(message.getText());
timestamp.setText(DateFormat.getTimeInstance().format(message.getDate())); timestamp.setText(DateFormat.getTimeInstance().format(message.getDate()));
clearBackground(); clearBackground();
if (CurrentUser.getInstance().isLocalId(message.getSender().id)) { if (CurrentUser.getInstance().getId().equals(message.getSender().id)) {
container.setAlignment(Pos.CENTER_RIGHT); container.setAlignment(Pos.CENTER_RIGHT);
button.getStyleClass().add("message-self"); button.getStyleClass().add("message-self");
} else { } else {

View file

@ -1,29 +1,19 @@
package fr.insa.clavardator.users; package fr.insa.clavardator.users;
import fr.insa.clavardator.db.DatabaseController; import fr.insa.clavardator.db.DatabaseController;
import fr.insa.clavardator.network.NetUtil;
import fr.insa.clavardator.util.ErrorCallback; import fr.insa.clavardator.util.ErrorCallback;
import fr.insa.clavardator.util.Log; import fr.insa.clavardator.util.Log;
import org.jetbrains.annotations.Nullable;
import java.net.InetAddress; import java.util.UUID;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import static fr.insa.clavardator.network.NetUtil.getIdFromIp;
public class CurrentUser extends User { public class CurrentUser extends User {
private static CurrentUser instance = null; private static CurrentUser instance = null;
private State state; private State state;
private ArrayList<Integer> validIds;
private CurrentUser() { private CurrentUser() {
super(); super();
state = State.UNINITIALIZED; state = State.UNINITIALIZED;
validIds = new ArrayList<>();
} }
/** /**
@ -35,48 +25,49 @@ public class CurrentUser extends User {
return instance; return instance;
} }
public void init(@Nullable InitCallback callback, ErrorCallback errorCallback) { private static String generateUniqueId() {
try { return UUID.randomUUID().toString();
final List<InetAddress> addresses = NetUtil.listAllLocalAddresses();
// Save all addresses for this user
validIds = new ArrayList<>();
addresses.forEach(address -> validIds.add(getIdFromIp(address)));
if (validIds.size() > 0) {
// Set the first id as the main one
id = validIds.get(0);
} else {
errorCallback.onError(new SocketException("No valid IP found"));
return;
} }
public void init(ErrorCallback errorCallback) {
final DatabaseController db = new DatabaseController(); final DatabaseController db = new DatabaseController();
// Make sure the user is in the db then set its username db.getCurrentUser((user) -> {
db.addUser(new UserInformation(this), () -> { if (user == null) {
db.getUsername(id, username -> { id = generateUniqueId();
if (username != null) { Log.v(getClass().getSimpleName(), "No previous user found, generating id: " + id);
Log.v(getClass().getSimpleName(), "Last username found : " + username); db.addCurrentUser(
setUsername(username); new UserInformation(this),
() -> setState(State.NONE),
errorCallback);
} else { } else {
Log.v(getClass().getSimpleName(), "No last username found, asking user..."); id = user.id;
if (user.getUsername() != null) {
Log.v(getClass().getSimpleName(), "Last user found : " + id + " / " + getUsername());
setUsername(user.getUsername());
} else {
Log.v(getClass().getSimpleName(), "No username found, asking user");
setState(State.NONE); setState(State.NONE);
} }
if (callback != null) {
callback.onFinish();
} }
}, errorCallback); }, errorCallback);
}, errorCallback);
} catch (SocketException e) {
errorCallback.onError(e);
}
} }
@Override @Override
public void setUsername(String newUsername) { public void setUsername(String newUsername) {
Log.v(this.getClass().getSimpleName(),
"Username changed from " + getUsername() + " to " + newUsername);
super.setUsername(newUsername); super.setUsername(newUsername);
final DatabaseController db = new DatabaseController();
db.updateCurrentUsername(new UserInformation(this),
null,
e -> Log.e(getClass().getSimpleName(), "Unable to update the username", e));
Log.v(this.getClass().getSimpleName(),
"Username changed to " + getUsername());
setState(State.VALID); setState(State.VALID);
} }
public State getState() {
return state;
}
public void setState(State state) { public void setState(State state) {
Log.v(this.getClass().getSimpleName(), Log.v(this.getClass().getSimpleName(),
"State changed from " + this.state.toString() + " to " + state.toString()); "State changed from " + this.state.toString() + " to " + state.toString());
@ -84,14 +75,6 @@ public class CurrentUser extends User {
this.state = state; this.state = state;
} }
public State getState() {
return state;
}
public boolean isLocalId(int id) {
return validIds.contains(id);
}
public enum State { public enum State {
UNINITIALIZED, UNINITIALIZED,
VALID, VALID,

View file

@ -2,6 +2,7 @@ package fr.insa.clavardator.users;
import fr.insa.clavardator.chat.ChatHistory; import fr.insa.clavardator.chat.ChatHistory;
import fr.insa.clavardator.chat.Message; import fr.insa.clavardator.chat.Message;
import fr.insa.clavardator.db.DatabaseController;
import fr.insa.clavardator.errors.UsernameTakenException; import fr.insa.clavardator.errors.UsernameTakenException;
import fr.insa.clavardator.network.PeerConnection; import fr.insa.clavardator.network.PeerConnection;
import fr.insa.clavardator.util.ErrorCallback; import fr.insa.clavardator.util.ErrorCallback;
@ -19,16 +20,21 @@ public class PeerUser extends User implements Comparable<PeerUser> {
private State state = State.DISCONNECTED; private State state = State.DISCONNECTED;
private transient PeerConnection connection; private transient PeerConnection connection;
public PeerUser(int id, String username) { public PeerUser(String id, String username) {
super(id, username); super(id, username);
history = new ChatHistory(this); history = new ChatHistory(this);
} }
public PeerUser(int id) { public PeerUser(String id) {
super(id); super(id);
history = new ChatHistory(this); history = new ChatHistory(this);
} }
public PeerUser() {
super();
history = new ChatHistory(this);
}
/** /**
* Asynchronously connects to the user and receives its information (id, username) * Asynchronously connects to the user and receives its information (id, username)
* *
@ -127,6 +133,7 @@ public class PeerUser extends User implements Comparable<PeerUser> {
UserInformation userInfo = (UserInformation) msg; UserInformation userInfo = (UserInformation) msg;
final String receivedUsername = userInfo.getUsername(); final String receivedUsername = userInfo.getUsername();
if (!receivedUsername.equals(CurrentUser.getInstance().getUsername())) { if (!receivedUsername.equals(CurrentUser.getInstance().getUsername())) {
setId(userInfo.id);
setUsername(receivedUsername); setUsername(receivedUsername);
callback.onUserConnected(); callback.onUserConnected();
subscribeToMessages(e -> { subscribeToMessages(e -> {
@ -162,7 +169,7 @@ public class PeerUser extends User implements Comparable<PeerUser> {
msg -> { msg -> {
Log.v(this.getClass().getSimpleName(), "Received message from " + id); Log.v(this.getClass().getSimpleName(), "Received message from " + id);
if (msg instanceof UserInformation) { if (msg instanceof UserInformation) {
assert ((UserInformation) msg).id == getId(); assert ((UserInformation) msg).id.equals(getId());
final String receivedUsername = ((UserInformation) msg).getUsername(); final String receivedUsername = ((UserInformation) msg).getUsername();
Log.v(this.getClass().getSimpleName(), "Message username: " + receivedUsername); Log.v(this.getClass().getSimpleName(), "Message username: " + receivedUsername);
if (CurrentUser.getInstance().getUsername().equals(receivedUsername)) { if (CurrentUser.getInstance().getUsername().equals(receivedUsername)) {
@ -171,8 +178,7 @@ public class PeerUser extends User implements Comparable<PeerUser> {
setUsername(receivedUsername); setUsername(receivedUsername);
} }
} else if (msg instanceof Message) { } else if (msg instanceof Message) {
assert ((Message) msg).getRecipient().id != id; assert !((Message) msg).getRecipient().id.equals(id);
assert CurrentUser.getInstance().isLocalId(((Message) msg).getRecipient().id);
Log.v(this.getClass().getSimpleName(), "Message text: " + ((Message) msg).getText()); Log.v(this.getClass().getSimpleName(), "Message text: " + ((Message) msg).getText());
history.addMessage((Message) msg, errorCallback); history.addMessage((Message) msg, errorCallback);
@ -200,6 +206,15 @@ public class PeerUser extends User implements Comparable<PeerUser> {
setState(State.DISCONNECTED); setState(State.DISCONNECTED);
} }
@Override
protected void setUsername(String newUsername) {
super.setUsername(newUsername);
final DatabaseController db = new DatabaseController();
db.updateUsername(new UserInformation(this),
null,
e -> Log.e(getClass().getSimpleName(), "Unable to update the username", e));
}
/** /**
* Close the connection to this user * Close the connection to this user
*/ */

View file

@ -9,7 +9,7 @@ import java.io.Serializable;
public class User implements Serializable { public class User implements Serializable {
private String username; private String username;
protected int id; protected String id;
// Make this class observable // Make this class observable
protected final transient PropertyChangeSupport pcs = new PropertyChangeSupport(this); protected final transient PropertyChangeSupport pcs = new PropertyChangeSupport(this);
@ -23,20 +23,24 @@ public class User implements Serializable {
public User() { public User() {
} }
public User(int id) { public User(String id) {
this.id = id; this.id = id;
} }
public User(int id, String username) { public User(String id, String username) {
this.id = id; this.id = id;
this.username = username; this.username = username;
} }
public int getId() { public String getId() {
return id; return id;
} }
public void setId(String id) {
this.id = id;
}
public String getUsername() { public String getUsername() {
return username; return username;
} }
@ -44,8 +48,6 @@ public class User implements Serializable {
protected void setUsername(String newUsername) { protected void setUsername(String newUsername) {
pcs.firePropertyChange("username", this.username, newUsername); pcs.firePropertyChange("username", this.username, newUsername);
this.username = newUsername; this.username = newUsername;
final DatabaseController db = new DatabaseController();
db.updateUsername(new UserInformation(this), null, e -> Log.e(getClass().getSimpleName(), "Unable to update the username", e));
} }
@Override @Override

View file

@ -6,10 +6,10 @@ import java.io.Serializable;
* Class used to serialize useful user information * Class used to serialize useful user information
*/ */
public class UserInformation implements Serializable { public class UserInformation implements Serializable {
public final int id; public final String id;
private final String username; private final String username;
public UserInformation(int id, String username) { public UserInformation(String id, String username) {
this.id = id; this.id = id;
this.username = username; this.username = username;
} }

View file

@ -7,16 +7,17 @@ import fr.insa.clavardator.util.ErrorCallback;
import fr.insa.clavardator.util.Log; import fr.insa.clavardator.util.Log;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
import static fr.insa.clavardator.network.NetUtil.getIdFromIp;
public class UserList { public class UserList {
private final Map<Integer, PeerUser> inactiveUsers = new HashMap<>(); private final Map<String, PeerUser> inactiveUsers = new HashMap<>();
private final Map<Integer, PeerUser> activeUsers = new HashMap<>(); private final Map<String, PeerUser> activeUsers = new HashMap<>();
private final ArrayList<PeerUser> pendingUsers = new ArrayList<>();
private UserConnectionCallback newUsersObservers = null; private UserConnectionCallback newUsersObservers = null;
@ -57,9 +58,8 @@ public class UserList {
*/ */
public void discoverActiveUsers(ErrorCallback errorCallback) { public void discoverActiveUsers(ErrorCallback errorCallback) {
netDiscoverer.discoverActiveUsers("CLAVARDATOR_BROADCAST", (ipAddr, data) -> { netDiscoverer.discoverActiveUsers("CLAVARDATOR_BROADCAST", (ipAddr, data) -> {
int id = getIdFromIp(ipAddr); Log.v(this.getClass().getSimpleName(), "Discovered new user: " + data);
Log.v(this.getClass().getSimpleName(), "Discovered new user: " + id); final PeerUser user = createNewPendingUser(data.trim());
final PeerUser user = createNewUser(id);
if (user != null) { if (user != null) {
user.createConnection(ipAddr, () -> onUserConnectionSuccess(user), errorCallback); user.createConnection(ipAddr, () -> onUserConnectionSuccess(user), errorCallback);
} }
@ -72,14 +72,14 @@ public class UserList {
*/ */
public void startDiscoveryListening() { public void startDiscoveryListening() {
netDiscoverer.startDiscoveryListening( netDiscoverer.startDiscoveryListening(
"CLAVARDATOR_RESPONSE", CurrentUser.getInstance().getId(),
null, null,
Throwable::printStackTrace); Throwable::printStackTrace);
} }
public void retrievedPreviousUsers(UserListLoadedCallback onFinish, ErrorCallback errorCallback) { public void retrievedPreviousUsers(UserListLoadedCallback onFinish, ErrorCallback errorCallback) {
db.getAllUsers(users -> { db.getAllUsers(users -> {
users.forEach(user -> createNewUser(user.id, user.getUsername())); users.forEach(user -> createNewInactiveUser(user.id, user.getUsername()));
onFinish.onLoaded(); onFinish.onLoaded();
}, errorCallback); }, errorCallback);
} }
@ -92,9 +92,9 @@ public class UserList {
public void startUserListening(ErrorCallback errorCallback) { public void startUserListening(ErrorCallback errorCallback) {
connectionListener.acceptConnection( connectionListener.acceptConnection(
(clientSocket) -> { (clientSocket) -> {
final int id = getIdFromIp(clientSocket.getInetAddress()); Log.v(this.getClass().getSimpleName(),
Log.v(this.getClass().getSimpleName(), "new connection from user: " + id); "new connection from user at address: " + clientSocket.getInetAddress().toString());
final PeerUser user = createNewUser(id); final PeerUser user = createNewPendingUser();
if (user != null) { if (user != null) {
user.acceptConnection(clientSocket, () -> user.acceptConnection(clientSocket, () ->
onUserConnectionSuccess(user), errorCallback); onUserConnectionSuccess(user), errorCallback);
@ -113,6 +113,12 @@ public class UserList {
user.addObserver(evt -> userChangeObserver(user, evt)); user.addObserver(evt -> userChangeObserver(user, evt));
} }
private PeerUser createNewPendingUser() {
PeerUser user = new PeerUser();
addUserToPendingList(user);
return user;
}
/** /**
* Creates a new user from its id and puts it in the inactive user list. * Creates a new user from its id and puts it in the inactive user list.
* We first try to fetch it from active and inactive users to prevent duplicates. * We first try to fetch it from active and inactive users to prevent duplicates.
@ -121,7 +127,7 @@ public class UserList {
* @param id The new user's id. * @param id The new user's id.
* @return A new PeerUser, or null if the user is already connected * @return A new PeerUser, or null if the user is already connected
*/ */
private PeerUser createNewUser(int id) { private PeerUser createNewPendingUser(String id) {
// If already connected, warn and return // If already connected, warn and return
if (activeUsers.containsKey(id)) { if (activeUsers.containsKey(id)) {
Log.w(getClass().getSimpleName(), Log.w(getClass().getSimpleName(),
@ -135,7 +141,7 @@ public class UserList {
if (user == null) { if (user == null) {
// Username is set on TCP connection start or db fetch // Username is set on TCP connection start or db fetch
user = new PeerUser(id); user = new PeerUser(id);
inactiveUsers.put(id, user); addUserToPendingList(user);
} }
return user; return user;
} }
@ -147,15 +153,17 @@ public class UserList {
* *
* @param id The new user's id. * @param id The new user's id.
* @param username The new user's username. * @param username The new user's username.
* @return A new PeerUser, or null if the user is already connected
*/ */
private PeerUser createNewUser(int id, String username) { private void createNewInactiveUser(String id, String username) {
final PeerUser user = createNewUser(id); final PeerUser user = new PeerUser(id, username);
if (user != null) { inactiveUsers.put(id, user);
user.setUsername(username);
notifyNewUserObservers(user); notifyNewUserObservers(user);
} }
return user;
private void addUserToPendingList(PeerUser user) {
if (!pendingUsers.contains(user)) {
pendingUsers.add(user);
}
} }
/** /**
@ -164,7 +172,7 @@ public class UserList {
* @param user The user to move * @param user The user to move
*/ */
private void moveUserToActiveList(PeerUser user) { private void moveUserToActiveList(PeerUser user) {
final int id = user.id; final String id = user.id;
if (!inactiveUsers.containsKey(id)) { if (!inactiveUsers.containsKey(id)) {
if (activeUsers.containsKey(id)) { if (activeUsers.containsKey(id)) {
Log.w(getClass().getSimpleName(), "Tried to set state " + PeerUser.State.CONNECTED + " on an already connected user: user id " + id); Log.w(getClass().getSimpleName(), "Tried to set state " + PeerUser.State.CONNECTED + " on an already connected user: user id " + id);
@ -183,7 +191,7 @@ public class UserList {
* @param user The user to move * @param user The user to move
*/ */
private void moveUserToInactiveList(PeerUser user) { private void moveUserToInactiveList(PeerUser user) {
final int id = user.id; final String id = user.id;
if (!activeUsers.containsKey(id)) { if (!activeUsers.containsKey(id)) {
if (inactiveUsers.containsKey(id)) { if (inactiveUsers.containsKey(id)) {
Log.w(getClass().getSimpleName(), "Tried to set state " + PeerUser.State.DISCONNECTED + " on an already disconnected user: user id " + id); Log.w(getClass().getSimpleName(), "Tried to set state " + PeerUser.State.DISCONNECTED + " on an already disconnected user: user id " + id);

View file

@ -28,53 +28,53 @@ public class DatabaseTest {
latch2.countDown(); latch2.countDown();
}, Assertions::fail); }, Assertions::fail);
db.getChatHistory(new UserInformation(1, "Yohan"), new Date(0), new Date(), history -> { db.getChatHistory(new UserInformation("1", "Yohan"), new Date(0), new Date(), history -> {
assertEquals(history.size(), 0); assertEquals(history.size(), 0);
latch2.countDown(); latch2.countDown();
}, Assertions::fail); }, Assertions::fail);
latch2.await(); latch2.await();
CountDownLatch latch8 = new CountDownLatch(1); CountDownLatch latch8 = new CountDownLatch(1);
db.addUser(new UserInformation(3, null), latch8::countDown, Assertions::fail); db.addUser(new UserInformation("3", null), latch8::countDown, Assertions::fail);
latch8.await(); latch8.await();
CountDownLatch latch3 = new CountDownLatch(5); CountDownLatch latch3 = new CountDownLatch(5);
db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"), db.addMessage(new Message(new UserInformation("1", "Yohan"), new UserInformation("2", "Arnaud"),
new Date(1609843556860L), "Coucou Arnaud !"), latch3::countDown, Assertions::fail); new Date(1609843556860L), "Coucou Arnaud !"), latch3::countDown, Assertions::fail);
db.addMessage(new Message(new UserInformation(2, "Arnaud"), new UserInformation(1, "Yohan"), db.addMessage(new Message(new UserInformation("2", "Arnaud"), new UserInformation("1", "Yohan"),
new Date(1609843556861L), "Coucou Yohan !"), latch3::countDown, Assertions::fail); new Date(1609843556861L), "Coucou Yohan !"), latch3::countDown, Assertions::fail);
db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"), db.addMessage(new Message(new UserInformation("1", "Yohan"), new UserInformation("2", "Arnaud"),
new Date(1609843556862L), "Ça va ?"), latch3::countDown, Assertions::fail); new Date(1609843556862L), "Ça va ?"), latch3::countDown, Assertions::fail);
db.addMessage(new Message(new UserInformation(2, "Arnaud"), new UserInformation(1, "Yohan"), db.addMessage(new Message(new UserInformation("2", "Arnaud"), new UserInformation("1", "Yohan"),
new Date(1609843556863L), "Ouais et toi ?"), latch3::countDown, Assertions::fail); new Date(1609843556863L), "Ouais et toi ?"), latch3::countDown, Assertions::fail);
db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"), db.addMessage(new Message(new UserInformation("1", "Yohan"), new UserInformation("2", "Arnaud"),
new Date(1609843556864L), "Super !"), latch3::countDown, Assertions::fail); new Date(1609843556864L), "Super !"), latch3::countDown, Assertions::fail);
latch3.await(); latch3.await();
CountDownLatch latch4 = new CountDownLatch(2); CountDownLatch latch4 = new CountDownLatch(2);
db.getAllUsers(users -> { db.getAllUsers(users -> {
assertEquals(3, users.size()); assertEquals(3, users.size());
assertEquals(3, users.get(0).getId()); assertEquals("3", users.get(0).getId());
assertEquals(1, users.get(1).getId()); assertEquals("1", users.get(1).getId());
assertEquals(2, users.get(2).getId()); assertEquals("2", users.get(2).getId());
assertNull(users.get(0).getUsername()); assertNull(users.get(0).getUsername());
assertEquals("Yohan", users.get(1).getUsername()); assertEquals("Yohan", users.get(1).getUsername());
assertEquals("Arnaud", users.get(2).getUsername()); assertEquals("Arnaud", users.get(2).getUsername());
latch4.countDown(); latch4.countDown();
}, Assertions::fail); }, Assertions::fail);
db.getChatHistory(new UserInformation(1, "Yohan"), new Date(0), new Date(), history -> { db.getChatHistory(new UserInformation("1", "Yohan"), new Date(0), new Date(), history -> {
assertEquals(5, history.size()); assertEquals(5, history.size());
assertEquals("Coucou Arnaud !", history.get(0).getText()); assertEquals("Coucou Arnaud !", history.get(0).getText());
assertEquals(1, history.get(0).getSender().id); assertEquals("1", history.get(0).getSender().id);
assertEquals(2, history.get(0).getRecipient().id); assertEquals("2", history.get(0).getRecipient().id);
assertEquals("Yohan", history.get(0).getSender().getUsername()); assertEquals("Yohan", history.get(0).getSender().getUsername());
assertEquals("Arnaud", history.get(0).getRecipient().getUsername()); assertEquals("Arnaud", history.get(0).getRecipient().getUsername());
assertEquals("Ouais et toi ?", history.get(3).getText()); assertEquals("Ouais et toi ?", history.get(3).getText());
assertEquals(2, history.get(3).getSender().id); assertEquals("2", history.get(3).getSender().id);
assertEquals(1, history.get(3).getRecipient().id); assertEquals("1", history.get(3).getRecipient().id);
assertEquals("Arnaud", history.get(3).getSender().getUsername()); assertEquals("Arnaud", history.get(3).getSender().getUsername());
assertEquals("Yohan", history.get(3).getRecipient().getUsername()); assertEquals("Yohan", history.get(3).getRecipient().getUsername());
latch4.countDown(); latch4.countDown();
@ -82,16 +82,9 @@ public class DatabaseTest {
latch4.await(); latch4.await();
CountDownLatch latch5 = new CountDownLatch(1); CountDownLatch latch5 = new CountDownLatch(1);
db.updateUsername(new UserInformation(2, "Toto"), latch5::countDown, Assertions::fail); db.updateUsername(new UserInformation("2", "Toto"), latch5::countDown, Assertions::fail);
latch5.await(); latch5.await();
CountDownLatch latch6 = new CountDownLatch(1);
db.getUsername(2, username -> {
assertEquals("Toto", username);
latch6.countDown();
}, Assertions::fail);
latch6.await();
CountDownLatch latch7 = new CountDownLatch(1); CountDownLatch latch7 = new CountDownLatch(1);
db.resetTables(latch7::countDown, Assertions::fail); db.resetTables(latch7::countDown, Assertions::fail);
latch7.await(); latch7.await();