diff --git a/.gitignore b/.gitignore index d54e5ee..9155d00 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ **/build/ !src/**/build/ +# Ignore sqlite db files +/*.db + # Ignore Gradle GUI config gradle-app.setting diff --git a/src/main/java/fr/insa/clavardator/chat/ChatHistory.java b/src/main/java/fr/insa/clavardator/chat/ChatHistory.java index cde4d05..ade1d64 100644 --- a/src/main/java/fr/insa/clavardator/chat/ChatHistory.java +++ b/src/main/java/fr/insa/clavardator/chat/ChatHistory.java @@ -3,10 +3,12 @@ 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.util.Log; import javafx.application.Platform; -import java.util.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.Timer; +import java.util.TimerTask; public class ChatHistory { private final DatabaseController db; @@ -17,7 +19,7 @@ public class ChatHistory { public ChatHistory(PeerUser user) { this.user = user; - db = new DatabaseController(user); + db = new DatabaseController(); this.historyListener = new ArrayList<>(); this.messageListener = new ArrayList<>(); } diff --git a/src/main/java/fr/insa/clavardator/chat/FileMessage.java b/src/main/java/fr/insa/clavardator/chat/FileMessage.java index 8f9bb53..ca7b83e 100644 --- a/src/main/java/fr/insa/clavardator/chat/FileMessage.java +++ b/src/main/java/fr/insa/clavardator/chat/FileMessage.java @@ -1,6 +1,6 @@ package fr.insa.clavardator.chat; -import fr.insa.clavardator.users.User; +import fr.insa.clavardator.users.UserInformation; import java.io.File; import java.io.FileInputStream; @@ -13,7 +13,7 @@ public class FileMessage extends Message { private final byte[] rawFile; private final String fileName; - public FileMessage(User sender, User recipient, String filePath, Date date, String text) throws IOException { + public FileMessage(UserInformation sender, UserInformation recipient, Date date, String text, String filePath) throws IOException { super(sender, recipient, date, text); File file = new File(filePath); diff --git a/src/main/java/fr/insa/clavardator/chat/Message.java b/src/main/java/fr/insa/clavardator/chat/Message.java index e8c53ff..2c2cdf9 100644 --- a/src/main/java/fr/insa/clavardator/chat/Message.java +++ b/src/main/java/fr/insa/clavardator/chat/Message.java @@ -49,4 +49,14 @@ public class Message implements Serializable { public Date getDate() { return date; } + + @Override + public String toString() { + return "Message{" + + "text='" + text + '\'' + + ", date=" + date + + ", sender=" + sender + + ", recipient=" + recipient + + '}'; + } } diff --git a/src/main/java/fr/insa/clavardator/db/DatabaseController.java b/src/main/java/fr/insa/clavardator/db/DatabaseController.java index 03955b9..9ed9ecc 100644 --- a/src/main/java/fr/insa/clavardator/db/DatabaseController.java +++ b/src/main/java/fr/insa/clavardator/db/DatabaseController.java @@ -1,50 +1,243 @@ package fr.insa.clavardator.db; +import fr.insa.clavardator.chat.FileMessage; import fr.insa.clavardator.chat.Message; import fr.insa.clavardator.users.User; +import fr.insa.clavardator.users.UserInformation; +import fr.insa.clavardator.util.Log; +import java.sql.*; import java.util.ArrayList; import java.util.Date; public class DatabaseController { - private final User user; - - public DatabaseController(User user) { - this.user = user; - } + private Connection connection; public DatabaseController() { - user = null; + } + + public void connect() { + try { + Class.forName("org.sqlite.JDBC"); + connection = DriverManager.getConnection("jdbc:sqlite:clavardator.db"); + Log.v(getClass().getSimpleName(), "Opened database successfully"); + } catch (ClassNotFoundException | SQLException e) { + e.printStackTrace(); + } + } + + public void close() { + try { + connection.close(); + } catch (SQLException throwables) { + throwables.printStackTrace(); + } + } + + public void initTables() { + try { + Statement stmtMessageTable = connection.createStatement(); + String messageTableSql = + "CREATE TABLE IF NOT EXISTS message " + + "(id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + + " timestamp DATETIME NOT NULL, " + + " sender INTEGER NOT NULL, " + + " recipient INTEGER NOT NULL, " + + " text TEXT, " + + " file_path TEXT)"; + + Log.v(getClass().getSimpleName(), "Creating table message..."); + int rowsModified = stmtMessageTable.executeUpdate(messageTableSql); + Log.v(getClass().getSimpleName(), rowsModified + " rows modified"); + + stmtMessageTable.close(); + Statement stmtUserTable = connection.createStatement(); + String userTableSql = + "CREATE TABLE IF NOT EXISTS user " + + "(id INTEGER PRIMARY KEY NOT NULL," + + " username INTEGER NOT NULL)"; + + Log.v(getClass().getSimpleName(), "Creating table user..."); + rowsModified = stmtUserTable.executeUpdate(userTableSql); + Log.v(getClass().getSimpleName(), rowsModified + " rows modified"); + + stmtUserTable.close(); + } catch (SQLException throwables) { + throwables.printStackTrace(); + } + } + + public void resetTables() { + try { + Statement dropMessage = connection.createStatement(); + String dropMessageSql = "DROP TABLE message"; + + Log.v(getClass().getSimpleName(), "Dropping table message..."); + int rowsModified = dropMessage.executeUpdate(dropMessageSql); + Log.v(getClass().getSimpleName(), rowsModified + " rows modified"); + + dropMessage.close(); + + Statement dropUser = connection.createStatement(); + String dropUserSql = "DROP TABLE user"; + + Log.v(getClass().getSimpleName(), "Dropping table user..."); + rowsModified = dropUser.executeUpdate(dropUserSql); + Log.v(getClass().getSimpleName(), rowsModified + " rows modified"); + + dropUser.close(); + + initTables(); + + } catch (SQLException throwables) { + throwables.printStackTrace(); + } } /** * Fetches the list of users for which we already have a chat history + * * @param callback Function called when the request is done */ public void getAllUsers(UsersCallback callback) { + try { + Statement stmt = connection.createStatement(); + String sql = "SELECT * FROM user"; + Log.v(getClass().getSimpleName(), "Fetching users from db... "); + ResultSet res = stmt.executeQuery(sql); + Log.v(getClass().getSimpleName(), res.getFetchSize() + " rows fetched"); + + ArrayList userList = new ArrayList<>(); + while (res.next()) { + int id = res.getInt("id"); + String username = res.getString("username"); + userList.add(new User(id, username)); + } + res.close(); + stmt.close(); + + if (callback != null) { + callback.onUsersFetched(userList); + } + } catch (SQLException throwables) { + throwables.printStackTrace(); + } } /** * Adds a message to the database for this user - * @param message The message to add to the database - * @param callback Function called when the request is done + * + * @param message The message to add to the database + * @param callback Function called when the request is done */ public void addMessage(Message message, MessageCallback callback) { - callback.onMessageSaved(); + + try { + // Insert the new message + final String insertMessageSql = + "INSERT INTO message " + + "(timestamp, sender, recipient, text, file_path) " + + "VALUES (?, ?, ?, ?, ?)"; + PreparedStatement insertMsgStmt = connection.prepareStatement(insertMessageSql); + insertMsgStmt.setTimestamp(1, new Timestamp(message.getDate().getTime())); + insertMsgStmt.setInt(2, message.getSender().id); + insertMsgStmt.setInt(3, message.getRecipient().id); + insertMsgStmt.setString(4, message.getText()); + if (insertMsgStmt instanceof FileMessage) { + // TODO: store file in file system + Log.w(getClass().getSimpleName(), "Functionality not implemented: file has not been saved"); + // stmt.setString(5, ((FileMessage) stmt).getFileName()); + } else { + insertMsgStmt.setString(5, null); + } + Log.v(getClass().getSimpleName(), "Inserting message into db... "); + int rowsModified = insertMsgStmt.executeUpdate(); + Log.v(getClass().getSimpleName(), rowsModified + " rows modified"); + insertMsgStmt.close(); + + // Insert the user if not already existing + final String insertUserSql = + "INSERT OR IGNORE INTO user" + + "(id, username)" + + "VALUES (?, ?)"; + PreparedStatement insertUserStmt = connection.prepareStatement(insertUserSql); + // Add sender + insertUserStmt.setInt(1, message.getSender().id); + insertUserStmt.setString(2, message.getSender().getUsername()); + Log.v(getClass().getSimpleName(), "Inserting sender into db... "); + rowsModified = insertUserStmt.executeUpdate(); + Log.v(getClass().getSimpleName(), rowsModified + " rows modified"); + + // Add recipient + insertUserStmt.setInt(1, message.getRecipient().id); + insertUserStmt.setString(2, message.getRecipient().getUsername()); + Log.v(getClass().getSimpleName(), "Inserting recipient into db"); + rowsModified = insertUserStmt.executeUpdate(); + Log.v(getClass().getSimpleName(), rowsModified + " rows modified"); + + + insertUserStmt.close(); + + if (callback != null) { + callback.onMessageSaved(); + } + } catch (SQLException throwables) { + throwables.printStackTrace(); + } } /** * Get the chat history for a given time frame - * @param from the starting date - * @param to the ending date + * + * @param user the user for which to retrieve the history + * @param from the starting date + * @param to the ending date * @param callback Function called when the request is done */ - public void getChatHistory(Date from, Date to, HistoryCallback callback) { + public void getChatHistory(UserInformation user, Date from, Date to, HistoryCallback callback) { + try { + Statement stmt = connection.createStatement(); + String sql = + "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 " + + "FROM message JOIN user AS s ON sender = s.id " + + " JOIN user AS r ON recipient = r.id " + + "WHERE (s.id = " + user.id + " OR r.id = " + user.id + ") AND " + + "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(), res.getFetchSize() + " rows fetched"); + + ArrayList chatHistory = new ArrayList<>(); + while (res.next()) { + int sId = res.getInt("sender_id"); + String sUsername = res.getString("sender_username"); + int rId = res.getInt("recipient_id"); + String rUsername = res.getString("recipient_username"); + Date date = new Date(res.getTimestamp("timestamp").getTime()); + String text = res.getString("text"); + String filePath = res.getString("file_path"); + if (filePath == null) { + chatHistory.add(new Message(new UserInformation(sId, sUsername), new UserInformation(rId, rUsername), date, text)); + } else { + // TODO + // chatHistory.add(new FileMessage(new UserInformation(sId, sUsername), new UserInformation(rId, rUsername), date, text, filePath)); + } + } + res.close(); + stmt.close(); + if (callback != null) { + callback.onHistoryFetched(chatHistory); + } + } catch (SQLException throwables) { + throwables.printStackTrace(); + } } diff --git a/src/main/java/fr/insa/clavardator/users/User.java b/src/main/java/fr/insa/clavardator/users/User.java index a5f3e13..f029379 100644 --- a/src/main/java/fr/insa/clavardator/users/User.java +++ b/src/main/java/fr/insa/clavardator/users/User.java @@ -44,4 +44,9 @@ public class User implements Serializable { pcs.firePropertyChange("username", this.username, newUsername); this.username = newUsername; } + + @Override + public String toString() { + return "User " + id + '(' + username + ')'; + } } diff --git a/src/main/java/fr/insa/clavardator/users/UserInformation.java b/src/main/java/fr/insa/clavardator/users/UserInformation.java index a878895..137a13e 100644 --- a/src/main/java/fr/insa/clavardator/users/UserInformation.java +++ b/src/main/java/fr/insa/clavardator/users/UserInformation.java @@ -18,4 +18,9 @@ public class UserInformation implements Serializable { public String getUsername() { return username; } + + @Override + public String toString() { + return "UserInfo " + id + '(' + username + ')'; + } } diff --git a/src/main/java/fr/insa/clavardator/users/UserList.java b/src/main/java/fr/insa/clavardator/users/UserList.java index a8ee721..6b4956e 100644 --- a/src/main/java/fr/insa/clavardator/users/UserList.java +++ b/src/main/java/fr/insa/clavardator/users/UserList.java @@ -116,9 +116,9 @@ public class UserList { // Disconnection if (!activeUsers.containsKey(id)) { if (inactiveUsers.containsKey(id)) { - Log.e(getClass().getSimpleName(), "Tried to set state DISCONNECTED on an already disconnected user: user id " + id); + Log.w(getClass().getSimpleName(), "Tried to set state DISCONNECTED on an already disconnected user: user id " + id); } else { - Log.e(getClass().getSimpleName(), "Tried to set state DISCONNECTED on an unknown user: user id " + id); + Log.w(getClass().getSimpleName(), "Tried to set state DISCONNECTED on an unknown user: user id " + id); } return; } diff --git a/src/main/java/fr/insa/clavardator/util/Log.java b/src/main/java/fr/insa/clavardator/util/Log.java index e83cdc5..9c56110 100755 --- a/src/main/java/fr/insa/clavardator/util/Log.java +++ b/src/main/java/fr/insa/clavardator/util/Log.java @@ -13,7 +13,7 @@ public class Log { * 3: errors & warnings & debug, * 4: all */ - public static int verboseLevel = 0; + public static int verboseLevel = 4; private static void print(String prefix, String message, String mode, int requiredLevel, @Nullable Exception e) { if (verboseLevel >= requiredLevel) { diff --git a/src/test/java/fr/insa/clavardator/DatabaseTest.java b/src/test/java/fr/insa/clavardator/DatabaseTest.java new file mode 100644 index 0000000..c507fb1 --- /dev/null +++ b/src/test/java/fr/insa/clavardator/DatabaseTest.java @@ -0,0 +1,60 @@ +package fr.insa.clavardator; + +import fr.insa.clavardator.chat.Message; +import fr.insa.clavardator.db.DatabaseController; +import fr.insa.clavardator.users.UserInformation; +import org.junit.jupiter.api.Test; + +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DatabaseTest { + private final DatabaseController db = new DatabaseController(); + + @Test + void testDB() { + db.connect(); + db.resetTables(); + db.getAllUsers(users -> { + assertEquals(0, users.size()); + }); + + 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(); + } + + +}