Add Ability to Delete Messages Locally #70

Merged
delvh merged 6 commits from f/delete-messages into develop 2020-09-30 20:50:59 +02:00
11 changed files with 48 additions and 220 deletions
Showing only changes of commit 57e85f56e9 - Show all commits

View File

@ -0,0 +1,20 @@
package envoy.client.event;
import envoy.event.Event;
/**
* Conveys the deletion of a message between clients and server.
delvh marked this conversation as resolved Outdated
Outdated
Review

This does not make sense as this is a client-sided event.

This does not make sense as this is a client-sided event.
Outdated
Review

That's an artifact from when it was an Event in Common.

That's an artifact from when it was an Event in Common.
*
* @author Leon Hofmeister
* @since Envoy Common v0.3-beta
*/
public class MessageDeletion extends Event<Long> {
private static final long serialVersionUID = 1L;
/**
* @param messageID the ID of the deleted message
* @since Envoy Common v0.3-beta
*/
public MessageDeletion(long messageID) { super(messageID); }
}

View File

@ -78,7 +78,7 @@ public final class MessageControl extends Label {
// Delete message - if own message - action
if (ownMessage && client.isOnline()) {
final var deleteMenuItem = new MenuItem("Delete");
final var deleteMenuItem = new MenuItem("Delete locally");
deleteMenuItem.setOnAction(e -> MessageUtil.deleteMessage(message));
items.add(deleteMenuItem);
}

View File

@ -82,6 +82,7 @@ public class TextInputContextMenu extends ContextMenu {
copyMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
deleteMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
clearMI.disableProperty().bind(control.textProperty().isEmpty());
selectAllMI.disableProperty().bind(control.textProperty().isEmpty());
setOnShowing(e -> pasteMI.setDisable(!Clipboard.getSystemClipboard().hasString()));
selectAllMI.getProperties().put("refreshMenu", Boolean.TRUE);

View File

@ -223,8 +223,8 @@ public final class ChatScene implements EventListener, Restorable {
// The sender of the message is the recipient of the chat
// Exceptions: this user is the sender (sync) or group message (group is
// recipient)
final boolean ownMessage = message.getSenderID() == localDB.getUser().getID();
final var recipientID = message instanceof GroupMessage || ownMessage ? message.getRecipientID() : message.getSenderID();
final var ownMessage = message.getSenderID() == localDB.getUser().getID();
delvh marked this conversation as resolved
Review

I thought we don't declare primitives as var?

I thought we don't declare primitives as `var`?
Review

We don't?
Well, we have to decide quick since I reactived use var instad of field in my formatter, and this was the result of it.
Should I deactivate it?

We don't? Well, we have to decide quick since I reactived `use var instad of field` in my formatter, and this was the result of it. Should I deactivate it?
Review

You can deactivate it, however I will publish a new version of the formatter soon.

You can deactivate it, however I will publish a new version of the formatter soon.
Review

So, I don't have to change it?

So, I don't have to change it?
Review

It's up to you. With the new formatter, it will get changed anyway sooner or later.

It's up to you. With the new formatter, it will get changed anyway sooner or later.
final var recipientID = message instanceof GroupMessage || ownMessage ? message.getRecipientID() : message.getSenderID();
localDB.getChat(recipientID).ifPresent(chat -> {
chat.insert(message);
@ -308,13 +308,6 @@ public final class ChatScene implements EventListener, Restorable {
@Event(eventType = Logout.class, priority = 200)
private void onLogout() { eventBus.removeListener(this); }
@Event(priority = 200)
private void onMessageDeletion(MessageDeletion message) {
// Clearing the selection if the own user was the sender of this event
if (message.isOwnEvent()) Platform.runLater(() -> { messageList.getSelectionModel().clearSelection(); });
}
/**
* Initializes all {@code SystemCommands} used in {@code ChatScene}.
*
@ -412,7 +405,7 @@ public final class ChatScene implements EventListener, Restorable {
if (currentChat != null) {
topBarContactLabel.setText(currentChat.getRecipient().getName());
if (currentChat.getRecipient() instanceof User) {
final String status = ((User) currentChat.getRecipient()).getStatus().toString();
final var status = ((User) currentChat.getRecipient()).getStatus().toString();
topBarStatusLabel.setText(status);
topBarStatusLabel.getStyleClass().add(status.toLowerCase());
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
@ -420,7 +413,7 @@ public final class ChatScene implements EventListener, Restorable {
topBarStatusLabel.setText(currentChat.getRecipient().getContacts().size() + " members");
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
}
final Rectangle clip = new Rectangle();
final var clip = new Rectangle();
clip.setWidth(43);
clip.setHeight(43);
clip.setArcHeight(43);
@ -778,6 +771,13 @@ public final class ChatScene implements EventListener, Restorable {
pendingAttachment = messageAttachment;
}
/**
* Clears the current message selection
delvh marked this conversation as resolved Outdated
Outdated
Review

Append a dot to the end of the sentence.

Append a dot to the end of the sentence.
*
* @since Envoy Client v0.3-beta
*/
public void clearMessageSelection() { messageList.getSelectionModel().clearSelection(); }
@FXML
private void searchContacts() {
chats.setPredicate(contactSearch.getText().isBlank() ? c -> true

View File

@ -8,8 +8,9 @@ import java.util.logging.*;
import javafx.stage.FileChooser;
import envoy.client.data.*;
import envoy.client.event.MessageDeletion;
import envoy.client.ui.controller.ChatScene;
import envoy.data.Message;
import envoy.event.MessageDeletion;
import envoy.util.EnvoyLog;
import dev.kske.eventbus.EventBus;
@ -44,14 +45,13 @@ public class MessageUtil {
* @since Envoy Client v0.3-beta
*/
public static void deleteMessage(Message message) {
final var messageDeletionEvent = new MessageDeletion(message.getID());
messageDeletionEvent.setOwnEvent();
final var messageDeletionEvent = new MessageDeletion(message.getID());
final var controller = Context.getInstance().getSceneContext().getController();
if (controller.getClass().equals(ChatScene.class)) ((ChatScene) controller).clearMessageSelection();
delvh marked this conversation as resolved Outdated
Outdated
Review

You can use instanceof here, which is faster and more readable. Also, consider to comment this line.

You can use `instanceof` here, which is faster and more readable. Also, consider to comment this line.
// Removing the message locally
EventBus.getInstance().dispatch(messageDeletionEvent);
// Removing the message on the server and this chat's recipients
Context.getInstance().getClient().send(messageDeletionEvent);
logger.log(Level.FINEST, "message deletion was requested for " + message);
}
@ -64,7 +64,7 @@ public class MessageUtil {
*/
public static void forwardMessage(Message message) { logger.log(Level.FINEST, "message forwarding was requested for " + message); }
delvh marked this conversation as resolved Outdated
Outdated
Review

Why are we adding context menu actions and methods that are not implemented on a branch that has nothing to do with it?

By the way, most logger statements start with a capital letter, so if you decide to keep the method, please consider that.

Why are we adding context menu actions and methods that are not implemented on a branch that has nothing to do with it? By the way, most logger statements start with a capital letter, so if you decide to keep the method, please consider that.
/**selected
/**
* Quotes the given message.
* Currently not implemented.
*
@ -95,7 +95,7 @@ public class MessageUtil {
} else file = new File(downloadLocation, fileName);
// A file was selected
if (file != null) try (FileOutputStream fos = new FileOutputStream(file)) {
if (file != null) try (var fos = new FileOutputStream(file)) {
fos.write(message.getAttachment().getData());
logger.log(Level.FINE, "Attachment of message was saved at " + file.getAbsolutePath());
} catch (final IOException e) {

View File

@ -1,34 +0,0 @@
package envoy.event;
/**
* Conveys the deletion of a message between clients and server.
*
* @author Leon Hofmeister
* @since Envoy Common v0.3-beta
*/
public class MessageDeletion extends Event<Long> {
private static final long serialVersionUID = 1L;
private transient boolean ownEvent;
/**
* @param messageID the ID of the deleted message
* @since Envoy Common v0.3-beta
*/
public MessageDeletion(long messageID) { super(messageID); }
/**
* @return whether the current user was the creator of this event.
* @since Envoy Common v0.3-beta
*/
public boolean isOwnEvent() { return ownEvent; }
/**
* Marks this event as being sent by this user. Is needed for a bug free
* and efficient selection clearing.
*
* @since Envoy Common v0.3-beta
*/
public void setOwnEvent() { ownEvent = true; }
}

View File

@ -56,8 +56,7 @@ public final class Startup {
new NameChangeProcessor(),
new ProfilePicChangeProcessor(),
new PasswordChangeRequestProcessor(),
new IssueProposalProcessor(),
new MessageDeletionProcessor())));
new IssueProposalProcessor())));
// Initialize the current message ID
final var persistenceManager = PersistenceManager.getInstance();

View File

@ -1,7 +1,7 @@
package envoy.server.data;
import java.time.Instant;
import java.util.*;
import java.util.Set;
import javax.persistence.*;
@ -98,34 +98,6 @@ public abstract class Contact {
*/
public void setCreationDate(Instant creationDate) { this.creationDate = creationDate; }
/**
* Shortcut to convert a {@code Contact} into a {@code User}.
*
* @param contact the contact to convert
* @return the casted contact
* @throws IllegalStateException if the given contact is not a User
* @since Envoy Server v0.3-beta
*/
public static User toUser(Contact contact) {
if (!(contact instanceof User)) throw new IllegalStateException("Cannot cast a non user to a user");
return (User) contact;
}
/**
* Shortcut to convert a set of {@code Contact}s into a set of {@code User}s.
*
* @param contacts the contacts to convert
* @return the casted contacts
* @throws IllegalStateException if one of the given contacts is not a User
* @since Envoy Server v0.3-beta
*/
public static Set<User> toUser(Set<Contact> contacts) {
final var newSet = new HashSet<User>();
for (final var contact : contacts)
newSet.add(toUser(contact));
return newSet;
}
@Override
public String toString() { return String.format("%s[id=%d,name=%s,%d contact(s)]", getClass().getSimpleName(), id, name, contacts.size()); }
}

View File

@ -1,81 +0,0 @@
package envoy.server.data;
import java.util.*;
import javax.persistence.*;
/**
* Defines a message that has been deleted.
*
* @author Leon Hofmeister
* @since Envoy Server v0.3-beta
*/
@Entity
@Table(name = "deletionEvents")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public final class MessageDeletion {
@Id
@GeneratedValue
protected long messageID;
@ManyToOne(targetEntity = User.class)
protected Set<User> recipientsToInform;
/**
* Creates an instance of {@code DeletionEvent}.
*
* @since Envoy Server v0.3-beta
*/
public MessageDeletion() {}
/**
* Creates an instance of {@code MessageDeletion}.
*
* @param messageID the ID of the message
* @param recipientsToInform the recipientsToInform of the message<br>
* <strong>that have not yet been notified of its
* deletion</strong>
* @since Envoy Server v0.3-beta
*/
public MessageDeletion(long messageID, Set<User> recipientsToInform) {
this.messageID = messageID;
this.recipientsToInform = recipientsToInform;
}
/**
* @return the messageID
* @since Envoy Server v0.3-beta
*/
public long getMessageID() { return messageID; }
/**
* @param messageID the messageID to set
* @since Envoy Server v0.3-beta
*/
public void setMessageID(long messageID) { this.messageID = messageID; }
/**
* @return the recipients that have yet to be informed
* @since Envoy Server v0.3-beta
*/
public Set<User> getRecipientsToInform() { return recipientsToInform; }
/**
* @param recipientsToInform the recipients that have yet to be informed
* @since Envoy Server v0.3-beta
*/
public void setRecipientsToInform(Set<User> recipientsToInform) { this.recipientsToInform = recipientsToInform; }
/**
* @param user the user who has been informed of the message deletion
* @since Envoy Server v0.3-beta
*/
public void recipientInformed(User user) { recipientsToInform.remove(user); }
/**
* @param users the users that have been informed of the message deletion
* @since Envoy Server v0.3-beta
*/
public void recipientInformed(Collection<User> users) { recipientsToInform.removeAll(users); }
}

View File

@ -1,7 +1,7 @@
package envoy.server.data;
import java.time.Instant;
import java.util.*;
import java.util.List;
import javax.persistence.*;
@ -9,6 +9,8 @@ import envoy.data.User.UserStatus;
import envoy.server.net.ConnectionManager;
/**
* Contains operations used for data retrieval.
delvh marked this conversation as resolved Outdated
Outdated
Review

As its not only retrieval, but also storage, maybe change this to "... for persistence."

As its not only retrieval, but also storage, maybe change this to "... for persistence."
*
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @since Envoy Server Standalone v0.1-alpha
@ -100,35 +102,12 @@ public final class PersistenceManager {
public void deleteContact(Contact contact) { remove(contact); }
/**
* Deletes a {@link Message} in the database and creates a new
* {@link MessageDeletion} object for <strong>all</strong> recipients of the
* message.
* Deletes a {@link Message} in the database.
*
* @param message the {@link Message} to delete
* @return the created {@link MessageDeletion} object
* @since Envoy Server v0.3-beta
* @since Envoy Server Standalone v0.1-alpha
*/
public MessageDeletion deleteMessage(Message message) {
final var recipient = message.getRecipient();
return deleteMessage(message,
recipient instanceof Group ? Contact.toUser(getGroupByID(recipient.id).getContacts()) : Set.of(Contact.toUser(recipient)));
}
/**
* Deletes a {@link Message} in the database and creates a new
* {@link MessageDeletion} object for the given recipients of the message.
*
* @param message the {@link Message} to delete
* @param recipientsYetToInform the (sub)set of all recipients of that message
* @return the created {@link MessageDeletion} object
* @since Envoy Server v0.3-beta
*/
public MessageDeletion deleteMessage(Message message, Set<User> recipientsYetToInform) {
final MessageDeletion deletion = new MessageDeletion(message.id, recipientsYetToInform);
persist(deletion);
remove(message);
return deletion;
}
public void deleteMessage(Message message) { remove(message); }
/**
* Searches for a {@link User} with a specific ID.
@ -195,16 +174,6 @@ public final class PersistenceManager {
*/
public ConfigItem getConfigItemByID(String key) { return entityManager.find(ConfigItem.class, key); }
/**
* Searches for a {@link MessageDeletion} with the given message id.
*
* @param id the id of the message to search for
* @return the message deletion object with the specified ID or {@code null} if
* none is found
* @since Envoy Server v0.3-beta
*/
public MessageDeletion getMessageDeletionByID(long id) { return entityManager.find(MessageDeletion.class, id); }
/**
* Returns all messages received while being offline or the ones that have
* changed.

View File

@ -1,18 +0,0 @@
package envoy.server.processors;
import java.io.IOException;
import envoy.event.MessageDeletion;
import envoy.server.net.ObjectWriteProxy;
/**
* Listens for and handles incoming {@link MessageDeletion}s.
*
* @author Leon Hofmeister
* @since Envoy Server v0.3-beta
*/
public class MessageDeletionProcessor implements ObjectProcessor<MessageDeletion> {
@Override
public void process(MessageDeletion message, long socketID, ObjectWriteProxy writeProxy) throws IOException {}
}