Add support for file

This commit is contained in:
Yohan Simard 2021-01-06 09:56:12 +01:00
parent 97c0d34159
commit ad48bfa05f
10 changed files with 176 additions and 72 deletions

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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");

View file

@ -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);

View file

@ -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

View file

@ -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<PeerUser, String> 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");
}

View file

@ -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)) {

View file

@ -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<PeerUser> {
@ -51,6 +54,30 @@ public class PeerUser extends User implements Comparable<PeerUser> {
}
}
/**
* 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
*

View file

@ -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);

View file

@ -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));
}
}