From ad48bfa05fc7bce2a6a485f247316103547714a9 Mon Sep 17 00:00:00 2001 From: Yohan Simard Date: Wed, 6 Jan 2021 09:56:12 +0100 Subject: [PATCH] Add support for file --- .../fr/insa/clavardator/chat/FileMessage.java | 73 ++++++++++++++++--- .../insa/clavardator/chat/ImageMessage.java | 13 ++-- .../clavardator/db/DatabaseController.java | 23 ++++-- .../insa/clavardator/ui/MainController.java | 2 +- .../clavardator/ui/chat/ChatController.java | 13 ++-- .../ui/chat/ChatFooterController.java | 29 +++++--- .../ui/chat/MessageListItemController.java | 19 ++++- .../fr/insa/clavardator/users/PeerUser.java | 27 +++++++ .../fr/insa/clavardator/DatabaseTest.java | 33 +++++---- .../java/fr/insa/clavardator/FirstTest.java | 16 ---- 10 files changed, 176 insertions(+), 72 deletions(-) delete mode 100644 src/test/java/fr/insa/clavardator/FirstTest.java diff --git a/src/main/java/fr/insa/clavardator/chat/FileMessage.java b/src/main/java/fr/insa/clavardator/chat/FileMessage.java index ca7b83e..6bfa7ac 100644 --- a/src/main/java/fr/insa/clavardator/chat/FileMessage.java +++ b/src/main/java/fr/insa/clavardator/chat/FileMessage.java @@ -1,42 +1,95 @@ 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; +import java.io.FileOutputStream; import java.io.IOException; import java.util.Date; public class FileMessage extends Message { public static final long MAX_FILE_SIZE = 20 * 1024 * 1024; // 20 Mo + public static final String STORED_FILES_FOLDER = "clavardator_stored_files"; - private final byte[] rawFile; private final String fileName; + private String path; + /** + * Constructs a FileMessage + * + * @param sender The sender of the message + * @param recipient The recipient of the message + * @param date The sending date of the message + * @param text The text of the message + * @param filePath The path to the file + * @throws IOException If the file does not exist, is not readable, is not a file, or is too large + */ public FileMessage(UserInformation sender, UserInformation recipient, Date date, String text, String filePath) throws IOException { super(sender, recipient, date, text); File file = new File(filePath); if (!file.exists()) - throw new IOException("The file does not exist"); + throw new IOException("The file " + filePath + " does not exist"); if (!file.canRead()) - throw new IOException("The file is not readable"); + throw new IOException("The file " + filePath + " is not readable"); if (!file.isFile()) - throw new IOException("The path does not lead to a file"); + throw new IOException("The path " + filePath + " does not lead to a file"); if (file.length() > MAX_FILE_SIZE) - throw new IOException("The file is too large"); + throw new IOException("The file " + filePath + " is too large"); fileName = file.getName(); - - FileInputStream stream = new FileInputStream(file); - rawFile = stream.readAllBytes(); + path = filePath; } - public byte[] getRawFile() { - return rawFile; + public FileMessage(User sender, User recipient, Date date, String text, String filePath) throws IOException { + this(new UserInformation(sender), new UserInformation(recipient), date, text, filePath); } public String getFileName() { return fileName; } + + public String getPath() { + return path; + } + + /** + * Stores the file in the clavardator directory. + * The field {@code path} is then updated to point to the new location. + * + * @return the path to the file + */ + public String storeFile() throws IOException { + path = STORED_FILES_FOLDER + File.separatorChar + fileName; + + // Create directory + File dir = new File(STORED_FILES_FOLDER); + dir.mkdirs(); + + // Create new file + int extensionBeginning = fileName.lastIndexOf('.'); + String name = fileName; + String extension = ""; + if (extensionBeginning != -1) { + name = fileName.substring(0, extensionBeginning); + extension = fileName.substring(extensionBeginning); + } + File file = new File(path); + int suffix = 1; + while (!file.createNewFile()) { + path = STORED_FILES_FOLDER + File.separatorChar + name + "_" + suffix++ + extension; + file = new File(path); + } + + // write to the file + FileInputStream stream = new FileInputStream(fileName); + byte[] rawFile = stream.readAllBytes(); + + FileOutputStream ostream = new FileOutputStream(file); + ostream.write(rawFile); + ostream.close(); + return path; + } } \ No newline at end of file diff --git a/src/main/java/fr/insa/clavardator/chat/ImageMessage.java b/src/main/java/fr/insa/clavardator/chat/ImageMessage.java index 89f8ccb..491a972 100644 --- a/src/main/java/fr/insa/clavardator/chat/ImageMessage.java +++ b/src/main/java/fr/insa/clavardator/chat/ImageMessage.java @@ -1,15 +1,12 @@ package fr.insa.clavardator.chat; -import fr.insa.clavardator.users.User; +import fr.insa.clavardator.users.UserInformation; +import java.io.IOException; import java.util.Date; -public class ImageMessage extends Message { - public ImageMessage(User sender, User recipient, Date date) { - super(sender, recipient, date); - } - - public ImageMessage(User sender, User recipient, Date date, String text) { - super(sender, recipient, date, text); +public class ImageMessage extends FileMessage { + public ImageMessage(UserInformation sender, UserInformation recipient, Date date, String text, String filePath) throws IOException { + super(sender, recipient, date, text, filePath); } } diff --git a/src/main/java/fr/insa/clavardator/db/DatabaseController.java b/src/main/java/fr/insa/clavardator/db/DatabaseController.java index 84e429c..a22ffa4 100644 --- a/src/main/java/fr/insa/clavardator/db/DatabaseController.java +++ b/src/main/java/fr/insa/clavardator/db/DatabaseController.java @@ -9,6 +9,7 @@ import fr.insa.clavardator.util.Log; import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.sql.*; import java.util.ArrayList; import java.util.Date; @@ -246,11 +247,14 @@ public class DatabaseController { addUser(message.getCorrespondent(), () -> { // Handle messages containing a file String filePath = "NULL"; - // TODO: Handle messages containing files: - // store file in file system and put the path in filePath if (message instanceof FileMessage) { - Log.w(getClass().getSimpleName(), "Functionality not implemented: file has not been saved"); -// filePath = ((FileMessage) message).getFileName(); + try { + filePath = "'" + ((FileMessage) message).storeFile() + "'"; + } catch (IOException e) { + Log.e(getClass().getSimpleName(), "Error while saving the file", e); + errorCallback.onError(e); + return; + } } String recipientId = message.getRecipient().id; @@ -263,7 +267,7 @@ public class DatabaseController { message.getDate().getTime() + ", " + "'" + senderId + "', " + "'" + recipientId + "', " + - "\"" + message.getText() + "\", " + + "'" + message.getText() + "', " + filePath + ")"; Log.v(getClass().getSimpleName(), "Inserting message into db... "); @@ -356,8 +360,13 @@ public class DatabaseController { if (filePath == null) { chatHistory.add(new Message(sender, recipient, date, text)); } else { - // TODO - // chatHistory.add(new FileMessage(new UserInformation(sId, sUsername), new UserInformation(rId, rUsername), date, text, filePath)); + try { + chatHistory.add(new FileMessage(sender, recipient, date, text, filePath)); + } catch (IOException e) { + Log.e(getClass().getSimpleName(), "Error while opening the file", e); + errorCallback.onError(e); + return; + } } } Log.v(getClass().getSimpleName(), chatHistory.size() + " messages fetched"); diff --git a/src/main/java/fr/insa/clavardator/ui/MainController.java b/src/main/java/fr/insa/clavardator/ui/MainController.java index b26b2f2..aba9bfc 100644 --- a/src/main/java/fr/insa/clavardator/ui/MainController.java +++ b/src/main/java/fr/insa/clavardator/ui/MainController.java @@ -238,7 +238,7 @@ public class MainController implements Initializable { snackbar = new JFXSnackbar(root); listController.setUserSelectedListener((user) -> chatController.setRemoteUser(user)); - chatController.setAttachmentListener(() -> System.out.println("attach event")); +// chatController.setAttachmentListener(() -> System.out.println("attach event")); chatController.setSendErrorListener((e) -> showSnackbarEvent("Erreur: Message non envoyé", SnackbarController.Mode.ERROR)); toolbarController.setEditListener(() -> openEditUsernameDialog(EditUsernameDialogController.Mode.EDIT)); toolbarController.setAboutListener(this::openAboutDialog); diff --git a/src/main/java/fr/insa/clavardator/ui/chat/ChatController.java b/src/main/java/fr/insa/clavardator/ui/chat/ChatController.java index f63e0e3..6db3eea 100644 --- a/src/main/java/fr/insa/clavardator/ui/chat/ChatController.java +++ b/src/main/java/fr/insa/clavardator/ui/chat/ChatController.java @@ -2,7 +2,6 @@ package fr.insa.clavardator.ui.chat; import fr.insa.clavardator.chat.ChatHistory; import fr.insa.clavardator.chat.Message; -import fr.insa.clavardator.ui.ButtonPressEvent; import fr.insa.clavardator.ui.LoadingScreenController; import fr.insa.clavardator.ui.NoSelectionModel; import fr.insa.clavardator.users.PeerUser; @@ -37,12 +36,12 @@ public class ChatController implements Initializable { private VBox emptyContainer; private PeerUser remoteUser; - public void setAttachmentListener(ButtonPressEvent listener) { - chatFooterController.setAttachmentListener(listener); - } - public void setSendErrorListener(ErrorCallback listener) { - chatFooterController.setSendErrorListener(listener); - } +// public void setAttachmentListener(ButtonPressEvent listener) { +// chatFooterController.setAttachmentListener(listener); +// } +public void setSendErrorListener(ErrorCallback listener) { + chatFooterController.setSendErrorListener(listener); +} /** * Check the user that finished loading is the right one then set the chat state to done diff --git a/src/main/java/fr/insa/clavardator/ui/chat/ChatFooterController.java b/src/main/java/fr/insa/clavardator/ui/chat/ChatFooterController.java index 66e416b..fa8f070 100644 --- a/src/main/java/fr/insa/clavardator/ui/chat/ChatFooterController.java +++ b/src/main/java/fr/insa/clavardator/ui/chat/ChatFooterController.java @@ -2,7 +2,6 @@ package fr.insa.clavardator.ui.chat; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXTextField; -import fr.insa.clavardator.ui.ButtonPressEvent; import fr.insa.clavardator.users.PeerUser; import fr.insa.clavardator.util.ErrorCallback; import fr.insa.clavardator.util.Log; @@ -12,8 +11,10 @@ import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.input.KeyCode; import javafx.scene.layout.HBox; +import javafx.stage.FileChooser; import java.beans.PropertyChangeEvent; +import java.io.File; import java.net.URL; import java.util.HashMap; import java.util.ResourceBundle; @@ -30,26 +31,31 @@ public class ChatFooterController implements Initializable { @FXML private JFXButton sendButton; - private ButtonPressEvent attachmentListeners; + // private ButtonPressEvent attachmentListeners; private ErrorCallback sendErrorListeners; private PeerUser remoteUser; private HashMap savedText; - public void setAttachmentListener(ButtonPressEvent listener) { - attachmentListeners = listener; - } + FileChooser fileChooser = new FileChooser(); + File attachedFile; + +// public void setAttachmentListener(ButtonPressEvent listener) { +// attachmentListeners = listener; +// } public void setSendErrorListener(ErrorCallback listener) { sendErrorListeners = listener; } public void onAttachmentPress() { - if (attachmentListeners != null) { - attachmentListeners.onPress(); - } +// if (attachmentListeners != null) { +// attachmentListeners.onPress(); +// } + attachedFile = fileChooser.showOpenDialog(container.getScene().getWindow()); } + public void onSendError(Exception e) { Log.e(this.getClass().getSimpleName(), "Error: Could not send message", e); if (sendErrorListeners != null) { @@ -63,7 +69,12 @@ public class ChatFooterController implements Initializable { public void onSend() { if (!isTextFieldEmpty()) { if (remoteUser != null) { - remoteUser.sendTextMessage(textField.getText(), this::onSendError); + if (attachedFile == null) { + remoteUser.sendTextMessage(textField.getText(), this::onSendError); + } else { + remoteUser.sendFileMessage(textField.getText(), attachedFile, this::onSendError); + attachedFile = null; + } } else { Log.e(this.getClass().getSimpleName(), "Error: remote user not set"); } diff --git a/src/main/java/fr/insa/clavardator/ui/chat/MessageListItemController.java b/src/main/java/fr/insa/clavardator/ui/chat/MessageListItemController.java index a606286..2d40ced 100644 --- a/src/main/java/fr/insa/clavardator/ui/chat/MessageListItemController.java +++ b/src/main/java/fr/insa/clavardator/ui/chat/MessageListItemController.java @@ -1,6 +1,7 @@ package fr.insa.clavardator.ui.chat; import com.jfoenix.controls.JFXButton; +import fr.insa.clavardator.chat.FileMessage; import fr.insa.clavardator.chat.Message; import fr.insa.clavardator.users.CurrentUser; import javafx.fxml.FXML; @@ -9,6 +10,9 @@ import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.layout.VBox; +import java.awt.*; +import java.io.File; +import java.io.IOException; import java.net.URL; import java.text.DateFormat; import java.util.ResourceBundle; @@ -32,7 +36,20 @@ public class MessageListItemController implements Initializable { public void setMessage(Message message) { if (!message.equals(currentMessage)) { currentMessage = message; - button.setText(message.getText()); + String text = message.getText(); + if (message instanceof FileMessage) { + FileMessage fileMessage = ((FileMessage) message); + text += "\n<" + fileMessage.getFileName() + ">"; + button.setOnMouseClicked(event -> { + Desktop desktop = Desktop.getDesktop(); + try { + desktop.open(new File(fileMessage.getPath())); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + button.setText(text); timestamp.setText(DateFormat.getTimeInstance().format(message.getDate())); clearBackground(); if (CurrentUser.getInstance().getId().equals(message.getSender().id)) { diff --git a/src/main/java/fr/insa/clavardator/users/PeerUser.java b/src/main/java/fr/insa/clavardator/users/PeerUser.java index d003fda..c004a8a 100644 --- a/src/main/java/fr/insa/clavardator/users/PeerUser.java +++ b/src/main/java/fr/insa/clavardator/users/PeerUser.java @@ -1,6 +1,7 @@ package fr.insa.clavardator.users; import fr.insa.clavardator.chat.ChatHistory; +import fr.insa.clavardator.chat.FileMessage; import fr.insa.clavardator.chat.Message; import fr.insa.clavardator.db.DatabaseController; import fr.insa.clavardator.errors.UsernameTakenException; @@ -11,6 +12,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.EOFException; +import java.io.File; +import java.io.IOException; import java.util.Date; public class PeerUser extends User implements Comparable { @@ -51,6 +54,30 @@ public class PeerUser extends User implements Comparable { } } + /** + * Sends a message containing a file to this user + * + * @param msg The text message to send + * @param errorCallback Callback on error + */ + public void sendFileMessage(String msg, File file, @Nullable ErrorCallback errorCallback) { + if (connection != null) { + Log.v(this.getClass().getSimpleName(), + "Sending file message to " + this.getUsername() + " / " + this.getId() + ": " + msg); + try { + final FileMessage message = new FileMessage(CurrentUser.getInstance(), this, new Date(), msg, file.getPath()); + connection.send(message, () -> history.addMessage(message, errorCallback), errorCallback); + } catch (IOException e) { + Log.e(this.getClass().getSimpleName(), "Could not send message: error while opening file", e); + if (errorCallback != null) { + errorCallback.onError(e); + } + } + } else { + Log.e(this.getClass().getSimpleName(), "Could not send message: connection is not initialized"); + } + } + /** * Sends current user information to this user * diff --git a/src/test/java/fr/insa/clavardator/DatabaseTest.java b/src/test/java/fr/insa/clavardator/DatabaseTest.java index 91a2a66..560ec3e 100644 --- a/src/test/java/fr/insa/clavardator/DatabaseTest.java +++ b/src/test/java/fr/insa/clavardator/DatabaseTest.java @@ -1,11 +1,13 @@ package fr.insa.clavardator; +import fr.insa.clavardator.chat.FileMessage; import fr.insa.clavardator.chat.Message; import fr.insa.clavardator.db.DatabaseController; import fr.insa.clavardator.users.UserInformation; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.io.File; import java.time.Duration; import java.util.Date; import java.util.concurrent.CountDownLatch; @@ -49,8 +51,8 @@ public class DatabaseTest { 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); + db.addMessage(new FileMessage(new UserInformation("1", "Yohan"), new UserInformation("2", "Arnaud"), + new Date(1609843556864L), "Super !", "clavardator_test.db"), latch3::countDown, Assertions::fail); latch3.await(); CountDownLatch latch4 = new CountDownLatch(2); @@ -69,18 +71,23 @@ public class DatabaseTest { new UserInformation("2", "Arnaud"), 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("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); + 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()); + + String path = FileMessage.STORED_FILES_FOLDER + File.separatorChar + ((FileMessage) history.get(4)).getFileName(); + File file = new File(path); + assertTrue(file.exists()); + file.delete(); + latch4.countDown(); + }, Assertions::fail); latch4.await(); CountDownLatch latch5 = new CountDownLatch(1); diff --git a/src/test/java/fr/insa/clavardator/FirstTest.java b/src/test/java/fr/insa/clavardator/FirstTest.java deleted file mode 100644 index 480131c..0000000 --- a/src/test/java/fr/insa/clavardator/FirstTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package fr.insa.clavardator; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -// See here: https://junit.org/junit5/docs/current/user-guide/#overview - -public class FirstTest { - private final TestClass t = new TestClass(); - - @Test - void addition() { - assertEquals(2, t.test(2)); - } -}