Connect ui to db
This commit is contained in:
parent
c2f53e89ec
commit
2099b78233
7 changed files with 206 additions and 103 deletions
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -64,6 +64,10 @@ public class CurrentUser extends User {
|
|||
this.state = state;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public enum State {
|
||||
UNINITIALIZED,
|
||||
VALID,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue