make DatabaseController async

This commit is contained in:
Yohan Simard 2021-01-05 15:34:24 +01:00
parent 6d3971be40
commit 1ab27f4f87
9 changed files with 359 additions and 258 deletions

View file

@ -3,6 +3,7 @@ package fr.insa.clavardator.chat;
import fr.insa.clavardator.db.DatabaseController; import fr.insa.clavardator.db.DatabaseController;
import fr.insa.clavardator.users.PeerUser; import fr.insa.clavardator.users.PeerUser;
import fr.insa.clavardator.users.UserInformation; import fr.insa.clavardator.users.UserInformation;
import fr.insa.clavardator.util.ErrorCallback;
import fr.insa.clavardator.util.Log; import fr.insa.clavardator.util.Log;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
@ -44,12 +45,12 @@ public class ChatHistory {
/** /**
* Loads history from database only if it has not previously been loaded * Loads history from database only if it has not previously been loaded
*/ */
public void load() { public void load(ErrorCallback errorCallback) {
if (!historyLoaded) { if (!historyLoaded) {
final Date from = new Date(); final Date from = new Date();
// Load whole history // Load whole history
from.setTime(0); from.setTime(0);
db.getChatHistory(new UserInformation(user), from, new Date(), this::onLoaded); db.getChatHistory(new UserInformation(user), from, new Date(), this::onLoaded, errorCallback);
} else { } else {
notifyHistoryLoaded(); notifyHistoryLoaded();
} }
@ -77,10 +78,10 @@ public class ChatHistory {
* *
* @param message The message to add * @param message The message to add
*/ */
public void addMessage(Message message) { public void addMessage(Message message, ErrorCallback errorCallback) {
db.addMessage(message, () -> { db.addMessage(message, () -> {
Platform.runLater(() -> history.add(message)); Platform.runLater(() -> history.add(message));
}); }, errorCallback);
} }
public interface HistoryLoadedCallback { public interface HistoryLoadedCallback {

View file

@ -5,6 +5,7 @@ import fr.insa.clavardator.chat.Message;
import fr.insa.clavardator.users.CurrentUser; import fr.insa.clavardator.users.CurrentUser;
import fr.insa.clavardator.users.User; import fr.insa.clavardator.users.User;
import fr.insa.clavardator.users.UserInformation; import fr.insa.clavardator.users.UserInformation;
import fr.insa.clavardator.util.ErrorCallback;
import fr.insa.clavardator.util.Log; import fr.insa.clavardator.util.Log;
import org.intellij.lang.annotations.Language; import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -21,7 +22,7 @@ public class DatabaseController {
} }
public DatabaseController(boolean test) { public DatabaseController(boolean test) {
if(test) { if (test) {
connectToTestDb(); connectToTestDb();
} else { } else {
connect(); connect();
@ -31,20 +32,22 @@ public class DatabaseController {
/** /**
* Connects to the main database * Connects to the main database
*/ */
public void connect() { private void connect() {
connectToDatabase("clavardator"); connectToDatabase("clavardator");
} }
/** /**
* Connects to the test database. * Connects to the test database.
* @implNote DO NOT USE OUTSIDE OF TESTS *
* @implNote DO NOT USE OUTSIDE OF TESTS
*/ */
public void connectToTestDb() { private void connectToTestDb() {
connectToDatabase("clavardator_test"); connectToDatabase("clavardator_test");
} }
/** /**
* Connects to the database of the given name * Connects to the database of the given name
*
* @param dbName The database to connect to * @param dbName The database to connect to
*/ */
private void connectToDatabase(String dbName) { private void connectToDatabase(String dbName) {
@ -68,128 +71,122 @@ public class DatabaseController {
} }
} }
/**
* Executes a simple update statement
*
* @param sqlQuery The query to execute
* @throws SQLException SQL error
*/
private void executeUpdate(@Language("SQL") String sqlQuery) throws SQLException {
Statement statement = connection.createStatement();
final int rowsModified = statement.executeUpdate(sqlQuery);
Log.v(getClass().getSimpleName(), rowsModified + " rows modified");
statement.close();
}
/** /**
* Creates the table used to store messages * Creates the table used to store messages
* @throws SQLException SQL error *
* @param callback The function to call on success
* @param errorCallback The function to call on error
*/ */
private void createMessageTable() throws SQLException { private void createMessageTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
Log.v(getClass().getSimpleName(), "Creating table message..."); @Language("SQL") String sql = "CREATE TABLE IF NOT EXISTS message " +
executeUpdate("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 INTEGER UNSIGNED NOT NULL, " +
" recipient INTEGER UNSIGNED NOT NULL, " + " recipient INTEGER UNSIGNED NOT NULL, " +
" text TEXT, " + " text TEXT, " +
" file_path TEXT)"); " file_path TEXT)";
Log.v(getClass().getSimpleName(), "Creating table message...");
UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
executor.start();
} }
/** /**
* Creates the table used to store users * Creates the table used to store users
* @throws SQLException SQL error *
* @param callback The function to call on success
* @param errorCallback The function to call on error
*/ */
private void createUserTable() throws SQLException { private void createUserTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
Log.v(getClass().getSimpleName(), "Creating table user..."); @Language("SQL") String sql = "CREATE TABLE IF NOT EXISTS user " +
executeUpdate("CREATE TABLE IF NOT EXISTS user " +
"(id INTEGER UNSIGNED PRIMARY KEY NOT NULL," + "(id INTEGER UNSIGNED PRIMARY KEY NOT NULL," +
" username TINYTEXT NULLABLE )"); " username TINYTEXT NULLABLE )";
Log.v(getClass().getSimpleName(), "Creating table user...");
UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
executor.start();
} }
/** /**
* Creates all needed tables if non-existent * Creates all needed tables if non-existent
*
* @param callback The function to call on success
* @param errorCallback The function to call on error
*/ */
public void init(@Nullable FinishCallback callback) { public void initTables(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
try { createMessageTable(() -> createUserTable(callback, errorCallback), errorCallback);
createMessageTable();
createUserTable();
if (callback != null) {
callback.onFinish();
}
} catch (SQLException e) {
e.printStackTrace();
}
} }
/** /**
* Destroys the message table * Destroys the message table
* @throws SQLException SQL error *
* @param callback The function to call on success
* @param errorCallback The function to call on error
*/ */
private void dropMessageTable() throws SQLException { private void dropMessageTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
@Language("SQL") String sql = "DROP TABLE IF EXISTS message";
Log.v(getClass().getSimpleName(), "Dropping table message..."); Log.v(getClass().getSimpleName(), "Dropping table message...");
executeUpdate("DROP TABLE IF EXISTS message"); UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
executor.start();
} }
/** /**
* Destroys the user table * Destroys the user table
* @throws SQLException SQL error *
* @param callback The function to call on success
* @param errorCallback The function to call on error
*/ */
private void dropUserTable() throws SQLException { private void dropUserTable(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
@Language("SQL") String sql = "DROP TABLE IF EXISTS user";
Log.v(getClass().getSimpleName(), "Dropping table user..."); Log.v(getClass().getSimpleName(), "Dropping table user...");
executeUpdate("DROP TABLE IF EXISTS user"); UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
executor.start();
} }
/** /**
* Destroys all tables * Destroys all tables
*
* @param callback The function to call on success
* @param errorCallback The function to call on error
*/ */
private void dropTables() { private void dropTables(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
try { dropMessageTable(() -> dropUserTable(callback, errorCallback), errorCallback);
dropMessageTable();
dropUserTable();
} catch (SQLException e) {
e.printStackTrace();
}
} }
/** /**
* Destroys and recreates all tables * Destroys and recreates all tables
*
* @param callback The function to call on success
* @param errorCallback The function to call on error
*/ */
public void resetTables() { public void resetTables(@Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
dropTables(); dropTables(() -> initTables(callback, errorCallback), errorCallback);
init(null);
} }
/** /**
* Fetches the list of users for which we already have a chat history * Fetches the list of users for which we already have a chat history
* *
* @param callback Function called when the request is done * @param callback Function called when the request is done
* @param errorCallback The function to call on error
*/ */
public void getAllUsers(UsersCallback callback) { public void getAllUsers(UsersCallback callback, ErrorCallback errorCallback) {
try { @Language("SQL") String sql = "SELECT * FROM user WHERE id != " + CurrentUser.getInstance().getId();
Statement stmt = connection.createStatement();
String sql = "SELECT * FROM user WHERE id != " + CurrentUser.getInstance().getId();
Log.v(getClass().getSimpleName(), "Fetching users from db... "); Log.v(getClass().getSimpleName(), "Fetching users from db... ");
ResultSet res = stmt.executeQuery(sql);
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"); int id = res.getInt("id");
String username = res.getString("username"); String username = res.getString("username");
userList.add(new User(id, username)); userList.add(new User(id, username));
} }
Log.v(getClass().getSimpleName(), userList.size() + " rows fetched"); Log.v(getClass().getSimpleName(), userList.size() + " users fetched");
res.close();
stmt.close();
if (callback != null) { callback.onUsersFetched(userList);
callback.onUsersFetched(userList); }, errorCallback);
} executor.start();
} catch (SQLException e) {
e.printStackTrace();
}
} }
@ -197,33 +194,37 @@ public class DatabaseController {
* Adds a message to the database for this user. * Adds a message to the database for this user.
* If the user does not exist, we create it. * If the user does not exist, we create it.
* *
* @param message The message to add to the database * @param message The message to add to the database
* @param callback Function called when the request is done * @param callback Function called when the request is done
* @param errorCallback The function to call on error
*/ */
public void addMessage(Message message, FinishCallback callback) { public void addMessage(Message message, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
try { // Insert the correspondent if not already in the database
Log.v(getClass().getSimpleName(), "Inserting correpondent into db... ");
addUser(message.getCorrespondent(), () -> {
// Handle messages containing a file
String filePath = "NULL";
// TODO: Handle messages containing files: // TODO: Handle messages containing files:
// store file in file system and put the path in filePath // store file in file system and put the path in filePath
String filePath = "NULL";
if (message instanceof FileMessage) { if (message instanceof FileMessage) {
Log.w(getClass().getSimpleName(), "Functionality not implemented: file has not been saved"); Log.w(getClass().getSimpleName(), "Functionality not implemented: file has not been saved");
// filePath = ((FileMessage) message).getFileName(); // filePath = ((FileMessage) message).getFileName();
} }
// Insert the new message // TODO: do this in the construction of the message instead?
Log.v(getClass().getSimpleName(), "Inserting message into db... ");
int recipientId; int recipientId;
int senderId; int senderId;
if (CurrentUser.getInstance().isLocalId(message.getRecipient().id)) { // if (CurrentUser.getInstance().isLocalId(message.getRecipient().id)) {
recipientId = CurrentUser.getInstance().getId(); // recipientId = CurrentUser.getInstance().getId();
senderId = message.getSender().id; senderId = message.getSender().id;
} else { // } else {
senderId = CurrentUser.getInstance().getId(); // senderId = CurrentUser.getInstance().getId();
recipientId = message.getRecipient().id; recipientId = message.getRecipient().id;
} // }
executeUpdate("INSERT INTO message " + // Insert the new 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() + ", " +
@ -231,73 +232,57 @@ public class DatabaseController {
recipientId + ", " + recipientId + ", " +
"\"" + message.getText() + "\", " + "\"" + message.getText() + "\", " +
filePath + filePath +
")"); ")";
Log.v(getClass().getSimpleName(), "Inserting message into db... ");
UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
executor.start();
Log.v(getClass().getSimpleName(), "Inserting correspondent into db... "); }, errorCallback);
addUser(message.getCorrespondent());
if (callback != null) {
callback.onFinish();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public void addUser(UserInformation user) {
addUser(user, null);
} }
/** /**
* Inserts the given user if not existing * Inserts the given user if not existing
* *
* @param user The user information to store * @param user The user information to store
* @param callback The function to call on success
* @param errorCallback The function to call on error
*/ */
public void addUser(UserInformation user, @Nullable FinishCallback callback) { public void addUser(UserInformation user, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
try { @Language("SQL") String sql;
Log.v(getClass().getSimpleName(), "Adding user to db: " + user.id + " / " + user.getUsername()); if (user.getUsername() != null) {
if (user.getUsername() != null) { sql = "INSERT OR IGNORE INTO user (id, username) " +
executeUpdate("INSERT OR IGNORE INTO user " + "VALUES (" + user.id + ", \"" + user.getUsername() + "\")";
"(id, username) " + } else {
"VALUES (" + user.id + ", \"" + user.getUsername() + "\")"); sql = "INSERT OR IGNORE INTO user (id) " +
} else { "VALUES (" + user.id + ")";
executeUpdate("INSERT OR IGNORE INTO user " +
"(id) " +
"VALUES (" + user.id + ")");
}
if (callback != null) {
callback.onFinish();
}
} catch (SQLException e) {
e.printStackTrace();
} }
Log.v(getClass().getSimpleName(), "Adding 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
* *
* @param user the user for which to retrieve the history * @param user the user for which to retrieve the history
* @param from the starting date * @param from the starting date
* @param to the ending date * @param to the ending date
* @param callback Function called when the request is done * @param callback Function called when the request is done
* @param errorCallback The function to call on error
*/ */
public void getChatHistory(UserInformation user, Date from, Date to, HistoryCallback callback) { public void getChatHistory(UserInformation user, Date from, Date to, HistoryCallback callback, ErrorCallback errorCallback) {
try { @Language("SQL") String sql =
Statement stmt = connection.createStatement(); "SELECT timestamp, s.id AS sender_id, s.username AS sender_username, " +
String sql = "r.id AS recipient_id, r.username AS recipient_username, text, file_path " +
"SELECT timestamp, s.id AS sender_id, s.username AS sender_username, " + "FROM message JOIN user AS s ON sender = s.id " +
"r.id AS recipient_id, r.username AS recipient_username, text, file_path " + " JOIN user AS r ON recipient = r.id " +
"FROM message JOIN user AS s ON sender = s.id " + "WHERE (s.id = " + user.id + " OR r.id = " + user.id + ") AND " +
" JOIN user AS r ON recipient = r.id " + "timestamp > " + from.getTime() + " AND timestamp < " + to.getTime() + " " +
"WHERE (s.id = " + user.id + " OR r.id = " + user.id + ") AND " + "ORDER BY timestamp";
"timestamp > " + from.getTime() + " AND timestamp < " + to.getTime() + " " +
"ORDER BY timestamp";
Log.v(getClass().getSimpleName(), "Fetching chat history from db... ");
ResultSet res = stmt.executeQuery(sql);
Log.v(getClass().getSimpleName(), "Fetching chat history from db... ");
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"); int sId = res.getInt("sender_id");
@ -314,49 +299,107 @@ public class DatabaseController {
// chatHistory.add(new FileMessage(new UserInformation(sId, sUsername), new UserInformation(rId, rUsername), date, text, filePath)); // chatHistory.add(new FileMessage(new UserInformation(sId, sUsername), new UserInformation(rId, rUsername), date, text, filePath));
} }
} }
Log.v(getClass().getSimpleName(), chatHistory.size() + " rows fetched"); Log.v(getClass().getSimpleName(), chatHistory.size() + " messages fetched");
res.close(); callback.onHistoryFetched(chatHistory);
stmt.close(); }, errorCallback);
if (callback != null) {
callback.onHistoryFetched(chatHistory); executor.start();
}
} catch (SQLException e) {
e.printStackTrace();
}
} }
public void updateUsername(UserInformation user) { public void updateUsername(UserInformation user, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
try { @Language("SQL") String sql = "UPDATE user SET username = '" + user.getUsername() + "' where id = " + user.id;
executeUpdate("UPDATE user SET " + UpdateExecutor executor = new UpdateExecutor(sql, callback, errorCallback);
"username = '" + user.getUsername() + "' where id = " + user.id); executor.start();
} catch (SQLException e) {
e.printStackTrace();
}
} }
public void getUsername(int id, UsernameCallback callback) { public void getUsername(int id, UsernameCallback callback, ErrorCallback errorCallback) {
try { @Language("SQL") String sql = "SELECT username from user where id =" + id;
Statement statement = connection.createStatement();
String sql = "SELECT username from user where id =" + id;
Log.v(getClass().getSimpleName(), "Fetching users from db... ");
ResultSet res = statement.executeQuery(sql);
Log.v(getClass().getSimpleName(), "Fetching username from db... ");
QueryExecutor executor = new QueryExecutor(sql, res -> {
String username = null; String username = null;
int nbRows = 0;
if (res.next()) { if (res.next()) {
username = res.getString("username"); username = res.getString("username");
nbRows = 1;
} }
Log.v(getClass().getSimpleName(), res.getFetchSize() + " rows fetched"); Log.v(getClass().getSimpleName(), nbRows + " username fetched");
res.close(); callback.onUsernameFetched(username);
statement.close(); }, errorCallback);
if (callback != null) { executor.start();
callback.onUsernameFetched(username); }
private class UpdateExecutor extends Thread {
private final String sqlQuery;
private final UpdateCallback callback;
private final ErrorCallback errorCallback;
/**
* Constructs a thread that executes an update on the database
*
* @param sqlQuery The query to execute
* @param callback The function to call on success
* @param errorCallback The function to call on error
*/
public UpdateExecutor(@Language("SQL") String sqlQuery, @Nullable DatabaseController.UpdateCallback callback, ErrorCallback errorCallback) {
this.sqlQuery = sqlQuery;
this.callback = callback;
this.errorCallback = errorCallback;
}
@Override
public void run() {
try {
Statement statement = connection.createStatement();
final int rowsModified = statement.executeUpdate(sqlQuery);
Log.v(getClass().getSimpleName(), rowsModified + " rows modified");
statement.close();
if (callback != null) {
callback.onUpdateExecuted();
}
} catch (SQLException e) {
Log.e(this.getClass().getSimpleName(), "Error executing update: ", e);
errorCallback.onError(e);
} }
} catch (SQLException e) {
e.printStackTrace();
} }
} }
private class QueryExecutor extends Thread {
private final String sqlQuery;
private final QueryCallback callback;
private final ErrorCallback errorCallback;
/**
* Constructs a thread that executes an update on the database
*
* @param sqlQuery The query to execute
* @param callback The function to call on success
* @param errorCallback The function to call on error
*/
public QueryExecutor(@Language("SQL") String sqlQuery, @Nullable QueryCallback callback, ErrorCallback errorCallback) {
this.sqlQuery = sqlQuery;
this.callback = callback;
this.errorCallback = errorCallback;
}
@Override
public void run() {
try {
Statement statement = connection.createStatement();
final ResultSet resultSet = statement.executeQuery(sqlQuery);
if (callback != null) {
callback.onQueryExecuted(resultSet);
}
resultSet.close();
statement.close();
} catch (SQLException e) {
Log.e(this.getClass().getSimpleName(), "Error executing update: ", e);
errorCallback.onError(e);
}
}
}
public interface UsersCallback { public interface UsersCallback {
void onUsersFetched(ArrayList<User> users); void onUsersFetched(ArrayList<User> users);
} }
@ -369,7 +412,11 @@ public class DatabaseController {
void onHistoryFetched(ArrayList<Message> history); void onHistoryFetched(ArrayList<Message> history);
} }
public interface FinishCallback { public interface UpdateCallback {
void onFinish(); void onUpdateExecuted();
}
private interface QueryCallback {
void onQueryExecuted(ResultSet resultSet) throws SQLException;
} }
} }

View file

@ -17,7 +17,6 @@ import javafx.fxml.Initializable;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import java.io.IOException; import java.io.IOException;
import java.net.SocketException;
import java.net.URL; import java.net.URL;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.Timer; import java.util.Timer;
@ -262,20 +261,22 @@ public class MainController implements Initializable {
toolbarController.setAboutListener(this::openAboutDialog); toolbarController.setAboutListener(this::openAboutDialog);
} }
private void onInitError(Exception e) {
Log.e("INIT", "Error during initialization", e);
showError();
}
/** /**
* Creates database if needed, then init current user, and finally load user list * Creates database if needed, then init current user, and finally load user list
*/ */
private void initDb() { private void initDb() {
final DatabaseController db = new DatabaseController(); final DatabaseController db = new DatabaseController();
db.init(() -> { db.initTables(() -> {
try { Log.v("INIT", "Current user: " + currentUser.getId());
Log.v("INIT", "Current user: " + currentUser.getId()); currentUser.init(() -> {
currentUser.init(() -> userList.retrievedPreviousUsers(this::onHistoryLoaded)); userList.retrievedPreviousUsers(this::onHistoryLoaded, this::onInitError);
} catch (SocketException e) { }, this::onInitError);
Log.e("INIT", "Could not init user", e); }, this::onInitError);
showError();
}
});
} }
/** /**

View file

@ -7,6 +7,7 @@ import fr.insa.clavardator.ui.LoadingScreenController;
import fr.insa.clavardator.ui.NoSelectionModel; import fr.insa.clavardator.ui.NoSelectionModel;
import fr.insa.clavardator.users.PeerUser; import fr.insa.clavardator.users.PeerUser;
import fr.insa.clavardator.util.ErrorCallback; import fr.insa.clavardator.util.ErrorCallback;
import fr.insa.clavardator.util.Log;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -73,7 +74,7 @@ public class ChatController implements Initializable {
// Make sure we always have the latest item on screen // Make sure we always have the latest item on screen
scrollToEnd(); scrollToEnd();
}); });
history.load(); history.load(e -> Log.e(getClass().getSimpleName(), "Error while loading message", e)/* TODO: show an error message? */);
} }
/** /**

View file

@ -2,7 +2,9 @@ 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.network.NetUtil;
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.net.InetAddress;
import java.net.SocketException; import java.net.SocketException;
@ -33,33 +35,38 @@ public class CurrentUser extends User {
return instance; return instance;
} }
public void init(InitCallback callback) throws SocketException { public void init(@Nullable InitCallback callback, ErrorCallback errorCallback) {
final List<InetAddress> addresses = NetUtil.listAllLocalAddresses(); try {
// Save all addresses for this user final List<InetAddress> addresses = NetUtil.listAllLocalAddresses();
validIds = new ArrayList<>(); // Save all addresses for this user
addresses.forEach(address -> validIds.add(getIdFromIp(address))); validIds = new ArrayList<>();
if (validIds.size() > 0) { addresses.forEach(address -> validIds.add(getIdFromIp(address)));
// Set the first id as the main one if (validIds.size() > 0) {
id = validIds.get(0); // Set the first id as the main one
} else { id = validIds.get(0);
throw new SocketException(); } else {
errorCallback.onError(new SocketException("No valid IP found"));
return;
}
final DatabaseController db = new DatabaseController();
// Make sure the user is in the db then set its username
db.addUser(new UserInformation(this), () -> {
db.getUsername(id, username -> {
if (username != null) {
Log.v(getClass().getSimpleName(), "Last username found : " + username);
setUsername(username);
} else {
Log.v(getClass().getSimpleName(), "No last username found, asking user...");
setState(State.NONE);
}
if (callback != null) {
callback.onFinish();
}
}, errorCallback);
}, errorCallback);
} catch (SocketException e) {
errorCallback.onError(e);
} }
final DatabaseController db = new DatabaseController();
// Make sure the user is in the db then set its username
db.addUser(new UserInformation(this), () -> {
db.getUsername(id, username -> {
if (username != null) {
Log.v(getClass().getSimpleName(), "Last username found : " + username);
setUsername(username);
} else {
Log.v(getClass().getSimpleName(), "No last username found, asking user...");
setState(State.NONE);
}
if (callback != null) {
callback.onFinish();
}
});
});
} }
@Override @Override

View file

@ -76,10 +76,7 @@ public class PeerUser extends User implements Comparable<PeerUser> {
Log.v(this.getClass().getSimpleName(), Log.v(this.getClass().getSimpleName(),
"Sending message to " + this.getUsername() + " / " + this.getId() + ": " + msg); "Sending message to " + this.getUsername() + " / " + this.getId() + ": " + msg);
final Message message = new Message(CurrentUser.getInstance(), this, new Date(), msg); final Message message = new Message(CurrentUser.getInstance(), this, new Date(), msg);
connection.send( connection.send(message, () -> history.addMessage(message, errorCallback), errorCallback);
message,
() -> history.addMessage(message),
errorCallback);
} else { } else {
Log.e(this.getClass().getSimpleName(), "Could not send message: connection is not initialized"); Log.e(this.getClass().getSimpleName(), "Could not send message: connection is not initialized");
} }
@ -175,8 +172,10 @@ public class PeerUser extends User implements Comparable<PeerUser> {
} }
} else if (msg instanceof Message) { } else if (msg instanceof Message) {
assert ((Message) msg).getRecipient().id != id; assert ((Message) msg).getRecipient().id != 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); history.addMessage((Message) msg, errorCallback);
} else if (msg instanceof UsernameTakenException) { } else if (msg instanceof UsernameTakenException) {
disconnect(); disconnect();
errorCallback.onError(new Exception("Received username already taken message")); errorCallback.onError(new Exception("Received username already taken message"));

View file

@ -1,6 +1,7 @@
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.util.Log;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport; import java.beans.PropertyChangeSupport;
@ -44,7 +45,7 @@ public class User implements Serializable {
pcs.firePropertyChange("username", this.username, newUsername); pcs.firePropertyChange("username", this.username, newUsername);
this.username = newUsername; this.username = newUsername;
final DatabaseController db = new DatabaseController(); final DatabaseController db = new DatabaseController();
db.updateUsername(new UserInformation(this)); db.updateUsername(new UserInformation(this), null, e -> Log.e(getClass().getSimpleName(), "Unable to update the username", e));
} }
@Override @Override

View file

@ -7,7 +7,6 @@ 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.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
@ -78,11 +77,11 @@ public class UserList {
Throwable::printStackTrace); Throwable::printStackTrace);
} }
public void retrievedPreviousUsers(UserListLoadedCallback onFinish) { public void retrievedPreviousUsers(UserListLoadedCallback onFinish, ErrorCallback errorCallback) {
db.getAllUsers(users -> { db.getAllUsers(users -> {
users.forEach(user -> createNewUser(user.id, user.getUsername())); users.forEach(user -> createNewUser(user.id, user.getUsername()));
onFinish.onLoaded(); onFinish.onLoaded();
}); }, errorCallback);
} }
/** /**

View file

@ -3,56 +3,101 @@ package fr.insa.clavardator;
import fr.insa.clavardator.chat.Message; import fr.insa.clavardator.chat.Message;
import fr.insa.clavardator.db.DatabaseController; import fr.insa.clavardator.db.DatabaseController;
import fr.insa.clavardator.users.UserInformation; import fr.insa.clavardator.users.UserInformation;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.time.Duration;
import java.util.Date; import java.util.Date;
import java.util.concurrent.CountDownLatch;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.*;
public class DatabaseTest { public class DatabaseTest {
private final DatabaseController db = new DatabaseController(true); private final DatabaseController db = new DatabaseController(true);
@Test @Test
void testDB() { void testDB() {
db.resetTables(); assertTimeoutPreemptively(Duration.ofSeconds(1), () -> {
db.getAllUsers(users -> { CountDownLatch latch1 = new CountDownLatch(1);
assertEquals(0, users.size()); db.resetTables(latch1::countDown, Assertions::fail);
latch1.await();
CountDownLatch latch2 = new CountDownLatch(2);
db.getAllUsers(users -> {
assertEquals(0, users.size());
latch2.countDown();
}, Assertions::fail);
db.getChatHistory(new UserInformation(1, "Yohan"), new Date(0), new Date(), history -> {
assertEquals(history.size(), 0);
latch2.countDown();
}, Assertions::fail);
latch2.await();
CountDownLatch latch8 = new CountDownLatch(1);
db.addUser(new UserInformation(3, null), latch8::countDown, Assertions::fail);
latch8.await();
CountDownLatch latch3 = new CountDownLatch(5);
db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"),
new Date(1609843556860L), "Coucou Arnaud !"), latch3::countDown, Assertions::fail);
db.addMessage(new Message(new UserInformation(2, "Arnaud"), new UserInformation(1, "Yohan"),
new Date(1609843556861L), "Coucou Yohan !"), latch3::countDown, Assertions::fail);
db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"),
new Date(1609843556862L), "Ça va ?"), latch3::countDown, Assertions::fail);
db.addMessage(new Message(new UserInformation(2, "Arnaud"), new UserInformation(1, "Yohan"),
new Date(1609843556863L), "Ouais et toi ?"), latch3::countDown, Assertions::fail);
db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"),
new Date(1609843556864L), "Super !"), latch3::countDown, Assertions::fail);
latch3.await();
CountDownLatch latch4 = new CountDownLatch(2);
db.getAllUsers(users -> {
assertEquals(3, users.size());
assertEquals(3, users.get(0).getId());
assertEquals(1, users.get(1).getId());
assertEquals(2, users.get(2).getId());
assertNull(users.get(0).getUsername());
assertEquals("Yohan", users.get(1).getUsername());
assertEquals("Arnaud", users.get(2).getUsername());
latch4.countDown();
}, Assertions::fail);
db.getChatHistory(new UserInformation(1, "Yohan"), new Date(0), new Date(), history -> {
assertEquals(5, history.size());
assertEquals("Coucou Arnaud !", history.get(0).getText());
assertEquals(1, history.get(0).getSender().id);
assertEquals(2, history.get(0).getRecipient().id);
assertEquals("Yohan", history.get(0).getSender().getUsername());
assertEquals("Arnaud", history.get(0).getRecipient().getUsername());
assertEquals("Ouais et toi ?", history.get(3).getText());
assertEquals(2, history.get(3).getSender().id);
assertEquals(1, history.get(3).getRecipient().id);
assertEquals("Arnaud", history.get(3).getSender().getUsername());
assertEquals("Yohan", history.get(3).getRecipient().getUsername());
latch4.countDown();
}, Assertions::fail);
latch4.await();
CountDownLatch latch5 = new CountDownLatch(1);
db.updateUsername(new UserInformation(2, "Toto"), latch5::countDown, Assertions::fail);
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);
db.resetTables(latch7::countDown, Assertions::fail);
latch7.await();
db.close();
}); });
db.getChatHistory(new UserInformation(1, "Yohan"), new Date(0), new Date(), history -> {
assertEquals(history.size(), 0);
});
db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"), new Date(), "Coucou Arnaud !"), null);
db.addMessage(new Message(new UserInformation(2, "Arnaud"), new UserInformation(1, "Yohan"), new Date(), "Coucou Yohan !"), null);
db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"), new Date(), "Ça va ?"), null);
db.addMessage(new Message(new UserInformation(2, "Arnaud"), new UserInformation(1, "Yohan"), new Date(), "Ouais et toi ?"), null);
db.addMessage(new Message(new UserInformation(1, "Yohan"), new UserInformation(2, "Arnaud"), new Date(), "Super !"), null);
db.getAllUsers(users -> {
assertEquals(2, users.size());
assertEquals(1, users.get(0).getId());
assertEquals(2, users.get(1).getId());
assertEquals("Yohan", users.get(0).getUsername());
assertEquals("Arnaud", users.get(1).getUsername());
});
db.getChatHistory(new UserInformation(1, "Yohan"), new Date(0), new Date(), history -> {
assertEquals(5, history.size());
assertEquals("Coucou Arnaud !", history.get(0).getText());
assertEquals(1, history.get(0).getSender().id);
assertEquals(2, history.get(0).getRecipient().id);
assertEquals("Yohan", history.get(0).getSender().getUsername());
assertEquals("Arnaud", history.get(0).getRecipient().getUsername());
assertEquals("Ouais et toi ?", history.get(3).getText());
assertEquals(2, history.get(3).getSender().id);
assertEquals(1, history.get(3).getRecipient().id);
assertEquals("Arnaud", history.get(3).getSender().getUsername());
assertEquals("Yohan", history.get(3).getRecipient().getUsername());
});
db.resetTables();
db.close();
} }