Connect ui to db

This commit is contained in:
Arnaud Vergnet 2021-01-04 10:33:39 +01:00
parent c2f53e89ec
commit 2099b78233
7 changed files with 206 additions and 103 deletions

View file

@ -1,71 +1,71 @@
package fr.insa.clavardator.chat;
import fr.insa.clavardator.db.DatabaseController;
import fr.insa.clavardator.users.CurrentUser;
import fr.insa.clavardator.users.PeerUser;
import fr.insa.clavardator.users.UserInformation;
import fr.insa.clavardator.util.Log;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Date;
public class ChatHistory {
private final DatabaseController db;
private final PeerUser user;
private final ObservableList<Message> history;
private final ArrayList<HistoryLoadedCallback> historyListener;
private final ArrayList<Message> fakeHistory;
public ChatHistory(PeerUser user) {
this.user = user;
db = new DatabaseController();
history = FXCollections.observableArrayList();
historyListener = new ArrayList<>();
fakeHistory = new ArrayList<>();
CurrentUser currentUser = CurrentUser.getInstance();
try {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
fakeHistory.add(new Message(user, currentUser, format.parse("2021-01-01 10:00:00"), "Coucou toi"));
} catch (ParseException e) {
e.printStackTrace();
}
}
/**
* Adds a listener for history loaded event
*
* @param listener The listener to add
*/
public void addHistoryLoadedListener(HistoryLoadedCallback listener) {
historyListener.add(listener);
}
/**
* Removes a listener for history loaded event
*
* @param listener The listener to remove
*/
public void removeHistoryLoadedListener(HistoryLoadedCallback listener) {
historyListener.remove(listener);
}
/**
* Notifies all listeners of a history loaded event
*/
private void notifyHistoryLoaded() {
historyListener.forEach(l -> l.onHistoryLoaded(user));
}
/**
* Loads history from database
*/
public void load() {
// db.getChatHistory(new Date(), new Date(), // TODO: put actual date
// newHistory -> {
// history = newHistory;
// notifyHistoryLoaded();
// });
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
public void run() {
Platform.runLater(() -> {
history.addAll(fakeHistory);
history.sort((message, t1) -> (int) (message.getDate().getTime() - t1.getDate().getTime()));
});
notifyHistoryLoaded();
t.cancel();
}
}, 1000);
db.getChatHistory(new UserInformation(user), new Date(), new Date(), this::onLoaded);
}
/**
* Adds all fetched messages to the active history
*
* @param newHistory The fetched history
*/
private void onLoaded(ArrayList<Message> newHistory) {
history.addAll(newHistory);
history.sort((message, t1) -> (int) (message.getDate().getTime() - t1.getDate().getTime()));
Log.v(getClass().getSimpleName(), "Message history loaded");
notifyHistoryLoaded();
}
public ObservableList<Message> getHistory() {
@ -73,17 +73,14 @@ public class ChatHistory {
}
/**
* @param message
* Saves the given message and adds it to the current history
*
* @param message The message to add
*/
public void addMessage(Message message) {
Platform.runLater(() -> history.add(message));
// db.addMessage(message, () -> {
// if (history == null) {
// history = new ArrayList<>();
// }
// history.add(message);
// notifyMessageAdded(message);
// });
db.addMessage(message, () -> {
Platform.runLater(() -> history.add(message));
});
}
public interface HistoryLoadedCallback {

View file

@ -15,6 +15,15 @@ public class DatabaseController {
private Connection connection;
public DatabaseController() {
connect();
}
public DatabaseController(boolean test) {
if(test) {
connectToTestDb();
} else {
connect();
}
}
/**
@ -22,6 +31,7 @@ public class DatabaseController {
*/
public void connect() {
connectToDatabase("clavardator");
initTables();
}
/**
@ -97,9 +107,9 @@ public class DatabaseController {
}
/**
* Creates all needed tables
* Creates all needed tables if non-existent
*/
private void createTables() {
private void initTables() {
try {
createMessageTable();
createUserTable();
@ -143,7 +153,7 @@ public class DatabaseController {
*/
public void resetTables() {
dropTables();
createTables();
initTables();
}

View file

@ -46,8 +46,10 @@ public class MainController implements Initializable {
private JFXSnackbar snackbar;
private UserList userList;
private boolean historyLoaded;
public MainController() {
historyLoaded = false;
currentUser = CurrentUser.getInstance();
currentUser.addObserver(propertyChangeEvent -> {
if (propertyChangeEvent.getPropertyName().equals("state")) {
@ -58,14 +60,26 @@ public class MainController implements Initializable {
}
private void onCurrentUserStateChange(CurrentUser.State newState) {
if (newState == CurrentUser.State.VALID)
startChat();
else {
final boolean isError = newState == CurrentUser.State.INVALID;
if (isError) {
endChat();
// Only do an action if the history is loaded
if (historyLoaded) {
if (newState == CurrentUser.State.VALID)
startChat();
else if (newState != CurrentUser.State.NONE) {
final boolean isError = newState == CurrentUser.State.INVALID;
if (isError) {
endChat();
}
Platform.runLater(() -> showLogin(isError));
}
Platform.runLater(() -> showLogin(isError));
}
}
public void onHistoryLoaded() {
historyLoaded = true;
final CurrentUser.State userState = CurrentUser.getInstance().getState();
// If the history loaded after the current user, fire the state change event again
if (userState != CurrentUser.State.NONE) {
onCurrentUserStateChange(userState);
}
}
@ -194,6 +208,7 @@ public class MainController implements Initializable {
public void setUserList(UserList userList) {
this.userList = userList;
userList.retrievedPreviousUsers(this::onHistoryLoaded);
listController.setUserList(userList);
listController.setRefreshUserListener(this::discoverActiveUsers);
editUserDialogController.setUserList(userList);

View file

@ -52,9 +52,9 @@ public class ChatController implements Initializable {
setState(State.LOADING);
if (this.remoteUser != null) {
final ChatHistory oldHistory = this.remoteUser.getHistory();
oldHistory.removeHistoryLoadedListener(onHistoryLoaded);
this.remoteUser.getHistory().removeHistoryLoadedListener(onHistoryLoaded);
}
this.remoteUser = remoteUser;
final ChatHistory history = remoteUser.getHistory();
history.addHistoryLoadedListener(onHistoryLoaded);
@ -64,7 +64,6 @@ public class ChatController implements Initializable {
scrollToEnd();
});
history.load();
this.remoteUser = remoteUser;
}
private void scrollToEnd() {

View file

@ -64,6 +64,10 @@ public class CurrentUser extends User {
this.state = state;
}
public State getState() {
return state;
}
public enum State {
UNINITIALIZED,
VALID,

View file

@ -29,7 +29,27 @@ public class UserList {
}
/**
* Discover all active users over the network. Observers are notified for each user discovered.
* Adds an observer for new user connection events
*
* @param connectionCallback The function to add as listener
*/
public void addNewUserObserver(UserConnectionCallback connectionCallback) {
newUsersObservers.add(connectionCallback);
}
/**
* Notifies observers the given user is a new connection
*
* @param user The newly connected user
*/
private void notifyNewUserObservers(PeerUser user) {
newUsersObservers.forEach(callback -> callback.onUserConnected(user));
}
/**
* Discovers all active users over the network
* and create a connection for each.
* Observers are notified for each new successful connection.
*
* @param errorCallback The function to call on error
* @see UserList#addNewUserObserver(UserConnectionCallback)
@ -45,6 +65,10 @@ public class UserList {
}, errorCallback);
}
/**
* Starts listening for other broadcasts.
* Only answers and waits for them to initiate the connection.
*/
public void startDiscoveryListening() {
netDiscoverer.startDiscoveryListening(
"CLAVARDATOR_RESPONSE",
@ -52,6 +76,18 @@ public class UserList {
Throwable::printStackTrace);
}
public void retrievedPreviousUsers(UserListLoadedCallback onFinish) {
db.getAllUsers(users -> {
users.forEach(user -> createNewUser(user.id, user.getUsername()));
onFinish.onLoaded();
});
}
/**
* Starts listening to user connection requests.
*
* @param errorCallback Callback on error
*/
public void startUserListening(ErrorCallback errorCallback) {
connectionListener.acceptConnection(
(clientSocket) -> {
@ -66,11 +102,24 @@ public class UserList {
errorCallback);
}
/**
* Notify observers and subscribe to user changes.
*
* @param user The newly connected user
*/
private void onUserConnectionSuccess(PeerUser user) {
notifyNewUserObservers(user);
user.addObserver(evt -> userChangeObserver(user, evt));
}
/**
* 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 do not notify observers yet as the connection may be canceled on username checks.
*
* @param id The new user's id.
* @return A new PeerUser, or null if the user is already connected
*/
private PeerUser createNewUser(int id) {
// If already connected, warn and return
if (activeUsers.containsKey(id)) {
@ -83,65 +132,92 @@ public class UserList {
PeerUser user = inactiveUsers.get(id);
// else create it
if (user == null) {
// Username is set on TCP connection start
// Username is set on TCP connection start or db fetch
user = new PeerUser(id);
inactiveUsers.put(id, user);
}
return user;
}
private void userChangeObserver(PeerUser user, PropertyChangeEvent evt) {
int id = user.id;
/**
* Creates a new user from its id and username and put it in inactive user list.
* We first try to fetch it from active and inactive users to prevent duplicates.
* We DO notify observers as the created user has all the information needed.
*
* @param id The new user's id.
* @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) {
final PeerUser user = createNewUser(id);
if (user != null) {
user.setUsername(username);
notifyNewUserObservers(user);
}
return user;
}
/**
* Tries to move the given user from inactive list to active list
*
* @param user The user to move
*/
private void moveUserToActiveList(PeerUser user) {
final int id = user.id;
if (!inactiveUsers.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);
} else {
Log.w(getClass().getSimpleName(), "Tried to set state " + PeerUser.State.CONNECTED + " on an unknown user: user id " + id);
}
return;
}
inactiveUsers.remove(user.id);
activeUsers.put(user.id, user);
}
/**
* Tries to move the given user from active list to inactive list
*
* @param user The user to move
*/
private void moveUserToInactiveList(PeerUser user) {
final int id = user.id;
if (!activeUsers.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);
} else {
Log.w(getClass().getSimpleName(), "Tried to set state " + PeerUser.State.DISCONNECTED + " on an unknown user: user id " + id);
}
return;
}
activeUsers.remove(id);
inactiveUsers.put(id, user);
}
/**
* Move user from active or inactive list when its state changes.
*
* @param user The user which changed
* @param evt The change event
*/
private void userChangeObserver(PeerUser user, PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("state")) {
PeerUser.State oldState = (PeerUser.State) evt.getOldValue();
PeerUser.State newState = (PeerUser.State) evt.getNewValue();
if ((oldState == PeerUser.State.DISCONNECTED || oldState == PeerUser.State.CONNECTING) &&
newState == PeerUser.State.CONNECTED) {
// Connection handling
if (!inactiveUsers.containsKey(id)) {
if (activeUsers.containsKey(id)) {
Log.w(getClass().getSimpleName(), "Tried to set state CONNECTED on an already connected user: user id " + id);
} else {
Log.w(getClass().getSimpleName(), "Tried to set state CONNECTED on an unknown user: user id " + id);
}
return;
}
inactiveUsers.remove(id);
activeUsers.put(id, user);
Log.v(getClass().getSimpleName(), "State of user " + id + " updated from " +
oldState.toString() + " to " + newState.toString());
if ((oldState == PeerUser.State.DISCONNECTED ||
(oldState == PeerUser.State.CONNECTING) && newState == PeerUser.State.CONNECTED)) {
moveUserToActiveList(user);
} else if (oldState == PeerUser.State.CONNECTED &&
(newState == PeerUser.State.DISCONNECTED || newState == PeerUser.State.CONNECTING)) {
// Disconnection
if (!activeUsers.containsKey(id)) {
if (inactiveUsers.containsKey(id)) {
Log.w(getClass().getSimpleName(), "Tried to set state DISCONNECTED on an already disconnected user: user id " + id);
} else {
Log.w(getClass().getSimpleName(), "Tried to set state DISCONNECTED on an unknown user: user id " + id);
}
return;
}
activeUsers.remove(id);
inactiveUsers.put(id, user);
Log.v(getClass().getSimpleName(), "State of user " + id + " updated from " +
oldState.toString() + " to " + newState.toString());
moveUserToInactiveList(user);
}
Log.v(getClass().getSimpleName(), "State of user " + user.id + " updated from " +
oldState.toString() + " to " + newState.toString());
}
}
public void addNewUserObserver(UserConnectionCallback connectionCallback) {
newUsersObservers.add(connectionCallback);
}
private void notifyNewUserObservers(PeerUser user) {
newUsersObservers.forEach(callback -> callback.onUserConnected(user));
}
/**
* Tests locally if a username is available
*
@ -155,7 +231,7 @@ public class UserList {
}
/**
* Tell all active users that our username changed
* Tells all active users that our username changed
*/
public void propagateUsernameChange() {
activeUsers.forEach((id, user) -> {
@ -164,13 +240,13 @@ public class UserList {
}
/**
* Close all running threads, sockets and db connection
* Must be called before exiting the app
* Closes all running threads, sockets and db connection.
* Must be called before exiting the app.
*/
public void destroy() {
netDiscoverer.stopDiscovery();
connectionListener.stopAccepting();
// db.close();
db.close();
for (PeerUser user : activeUsers.values()) {
user.disconnect();
}
@ -180,4 +256,7 @@ public class UserList {
void onUserConnected(PeerUser user);
}
public interface UserListLoadedCallback {
void onLoaded();
}
}

View file

@ -10,11 +10,10 @@ import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class DatabaseTest {
private final DatabaseController db = new DatabaseController();
private final DatabaseController db = new DatabaseController(true);
@Test
void testDB() {
db.connectToTestDb();
db.resetTables();
db.getAllUsers(users -> {
assertEquals(0, users.size());