Contact Deletion #97

Merged
delvh merged 6 commits from f/contact-deletion into develop 2020-10-19 18:09:21 +02:00
9 changed files with 122 additions and 20 deletions
Showing only changes of commit f97d61e58d - Show all commits

View File

@ -191,7 +191,6 @@ public final class ChatScene implements EventListener, Restorable {
// Set the design of the box in the upper-left corner // Set the design of the box in the upper-left corner
settingsButton.setAlignment(Pos.BOTTOM_RIGHT); settingsButton.setAlignment(Pos.BOTTOM_RIGHT);
HBox.setHgrow(spaceBetweenUserAndSettingsButton, Priority.ALWAYS);
generateOwnStatusControl(); generateOwnStatusControl();
Platform.runLater(() -> { Platform.runLater(() -> {
@ -727,6 +726,7 @@ public final class ChatScene implements EventListener, Restorable {
// Else prepend it to the HBox children // Else prepend it to the HBox children
final var ownUserControl = new ContactControl(localDB.getUser()); final var ownUserControl = new ContactControl(localDB.getUser());
ownUserControl.setAlignment(Pos.CENTER_LEFT); ownUserControl.setAlignment(Pos.CENTER_LEFT);
HBox.setHgrow(ownUserControl, Priority.NEVER);
ownContactControl.getChildren().add(0, ownUserControl); ownContactControl.getChildren().add(0, ownUserControl);
} }
} }

View File

@ -167,7 +167,7 @@
<HBox id="transparent-background" fx:id="ownContactControl"> <HBox id="transparent-background" fx:id="ownContactControl">
<children> <children>
<Region id="transparent-background" prefWidth="120" <Region id="transparent-background" prefWidth="120"
fx:id="spaceBetweenUserAndSettingsButton" /> fx:id="spaceBetweenUserAndSettingsButton" HBox.hgrow="ALWAYS" />
<Button fx:id="settingsButton" mnemonicParsing="false" <Button fx:id="settingsButton" mnemonicParsing="false"
onAction="#settingsButtonClicked" prefHeight="30.0" onAction="#settingsButtonClicked" prefHeight="30.0"
prefWidth="30.0" text="" alignment="CENTER"> prefWidth="30.0" text="" alignment="CENTER">

View File

@ -0,0 +1,15 @@
package envoy.event.contact;
import envoy.event.Event.Valueless;
/**
* Conveys that either a direct contact or a group member has been deleted while
* the user has been offline.
*
* @author Leon Hofmeister
* @since Envoy Common v0.3-beta
*/
public class ContactDeletionSinceLastLogin extends Valueless {
private static final long serialVersionUID = 1L;
}

View File

@ -105,7 +105,7 @@ public final class SerializationUtils {
file.createNewFile(); file.createNewFile();
} }
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) { try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
for (var obj : objs) for (final var obj : objs)
delvh marked this conversation as resolved Outdated
Outdated
Review

Please reset this class to develop.

Please reset this class to `develop`.
out.writeObject(obj); out.writeObject(obj);
} }
} }
@ -119,7 +119,7 @@ public final class SerializationUtils {
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha
*/ */
public static byte[] writeToByteArray(Object obj) throws IOException { public static byte[] writeToByteArray(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(); final var baos = new ByteArrayOutputStream();
try (ObjectOutputStream oout = new ObjectOutputStream(baos)) { try (ObjectOutputStream oout = new ObjectOutputStream(baos)) {
oout.writeObject(obj); oout.writeObject(obj);
} }
@ -137,10 +137,10 @@ public final class SerializationUtils {
*/ */
public static void writeBytesWithLength(Object obj, OutputStream out) throws IOException { public static void writeBytesWithLength(Object obj, OutputStream out) throws IOException {
// Serialize object to byte array // Serialize object to byte array
byte[] objBytes = writeToByteArray(obj); final var objBytes = writeToByteArray(obj);
// Get length of byte array in bytes // Get length of byte array in bytes
byte[] objLen = intToBytes(objBytes.length); final var objLen = intToBytes(objBytes.length);
// Write length and byte array // Write length and byte array
out.write(objLen); out.write(objLen);

View File

@ -55,7 +55,7 @@ public class Message {
@JoinColumn @JoinColumn
protected User sender; protected User sender;
@ManyToOne @ManyToOne(cascade = CascadeType.REMOVE)
@JoinColumn @JoinColumn
protected Contact recipient; protected Contact recipient;

View File

@ -97,7 +97,20 @@ public final class PersistenceManager {
* @param contact the {@link Contact} to delete * @param contact the {@link Contact} to delete
* @since Envoy Server Standalone v0.1-alpha * @since Envoy Server Standalone v0.1-alpha
*/ */
public void deleteContact(Contact contact) { remove(contact); } public void deleteContact(Contact contact) {
transaction(() -> {
// Remove this contact from the contact list of his contacts
for (final var remainingContact : contact.getContacts())
remainingContact.getContacts().remove(contact);
// Delete messages sent or received by this contact
entityManager.createQuery("DELETE FROM Message m WHERE (m.sender = :contact OR m.recipient = :contact )")
.setParameter("contact", contact)
.executeUpdate();
});
remove(contact);
}
/** /**
* Deletes a {@link Message} in the database. * Deletes a {@link Message} in the database.
@ -232,7 +245,7 @@ public final class PersistenceManager {
final var c1 = getContactByID(contactID1); final var c1 = getContactByID(contactID1);
final var c2 = getContactByID(contactID2); final var c2 = getContactByID(contactID2);
// Add users to each others contact lists // Add users to each others contact list
c1.getContacts().add(c2); c1.getContacts().add(c2);
c2.getContacts().add(c1); c2.getContacts().add(c1);
@ -240,6 +253,27 @@ public final class PersistenceManager {
transaction(() -> { entityManager.merge(c1); entityManager.merge(c2); }); transaction(() -> { entityManager.merge(c1); entityManager.merge(c2); });
} }
/**
* Removes a contact from the contact list of another contact and vice versa.
*
* @param contactID1 the ID of the first contact
* @param contactID2 the ID of the second contact
* @since Envoy Server v0.3-beta
*/
public void removeContactBidirectional(long contactID1, long contactID2) {
// Get users by ID
final var c1 = getContactByID(contactID1);
final var c2 = getContactByID(contactID2);
// Remove users from each others contact list
c1.getContacts().remove(c2);
c2.getContacts().remove(c1);
// Synchronize changes with the database
transaction(() -> { entityManager.merge(c1); entityManager.merge(c2); });
}
/** /**
* @param user the User whose contacts should be retrieved * @param user the User whose contacts should be retrieved
* @return the contacts of this User * @return the contacts of this User

View File

@ -70,6 +70,9 @@ public final class User extends Contact {
@Column(name = "last_seen") @Column(name = "last_seen")
private Instant lastSeen; private Instant lastSeen;
@Column(name = "latest_contact_deletion")
private Instant latestContactDeletion;
private UserStatus status; private UserStatus status;
@Override @Override
@ -140,4 +143,16 @@ public final class User extends Contact {
* @since Envoy Server Standalone v0.1-alpha * @since Envoy Server Standalone v0.1-alpha
*/ */
public void setStatus(UserStatus status) { this.status = status; } public void setStatus(UserStatus status) { this.status = status; }
/**
* @return the latestContactDeletion
* @since Envoy Server v0.3-beta
*/
public Instant getLatestContactDeletion() { return latestContactDeletion; }
/**
* @param latestContactDeletion the latestContactDeletion to set
* @since Envoy Server v0.3-beta
*/
public void setLatestContactDeletion(Instant latestContactDeletion) { this.latestContactDeletion = latestContactDeletion; }
} }

View File

@ -1,10 +1,11 @@
package envoy.server.processors; package envoy.server.processors;
import java.util.logging.Logger; import java.time.Instant;
import java.util.logging.*;
import envoy.event.ElementOperation; import envoy.event.ElementOperation;
import envoy.event.contact.ContactOperation; import envoy.event.contact.ContactOperation;
import envoy.server.data.PersistenceManager; import envoy.server.data.*;
import envoy.server.net.*; import envoy.server.net.*;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
@ -14,23 +15,55 @@ import envoy.util.EnvoyLog;
*/ */
public final class ContactOperationProcessor implements ObjectProcessor<ContactOperation> { public final class ContactOperationProcessor implements ObjectProcessor<ContactOperation> {
private static final ConnectionManager connectionManager = ConnectionManager.getInstance(); private static final ConnectionManager connectionManager = ConnectionManager.getInstance();
private static final Logger logger = EnvoyLog.getLogger(ContactOperationProcessor.class); private static final Logger logger = EnvoyLog.getLogger(ContactOperationProcessor.class);
private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
@Override @Override
public void process(ContactOperation evt, long socketId, ObjectWriteProxy writeProxy) { public void process(ContactOperation evt, long socketId, ObjectWriteProxy writeProxy) {
final long userID = ConnectionManager.getInstance().getUserIDBySocketID(socketId);
final long contactID = evt.get().getID();
final var sender = persistenceManager.getUserByID(userID);
switch (evt.getOperationType()) { switch (evt.getOperationType()) {
case ADD: case ADD:
final long userID = ConnectionManager.getInstance().getUserIDBySocketID(socketId); logger.log(Level.FINE, String.format("Adding %s to the contact list of user %d.", evt.get(), userID));
final long contactId = evt.get().getID(); persistenceManager.addContactBidirectional(userID, contactID);
logger.fine(String.format("Adding user %s to the contact list of user %d.%n", evt.get(), userID));
PersistenceManager.getInstance().addContactBidirectional(userID, contactId);
// Notify the contact if online // Notify the contact if online
if (ConnectionManager.getInstance().isOnline(contactId)) if (connectionManager.isOnline(contactID))
writeProxy.write(connectionManager.getSocketID(contactId), writeProxy.write(connectionManager.getSocketID(contactID), new ContactOperation(sender.toCommon(), ElementOperation.ADD));
new ContactOperation(PersistenceManager.getInstance().getUserByID(userID).toCommon(), ElementOperation.ADD)); break;
case REMOVE:
// Remove the relationships and notify sender if he logs in using another
// LocalDB
persistenceManager.removeContactBidirectional(userID, contactID);
final var contact = persistenceManager.getContactByID(contactID);
sender.setLatestContactDeletion(Instant.now());
// The sender wants to remove a normal contact
if (contact instanceof User) {
// Notify the removed contact on next startup(s) of this deletion
persistenceManager.getUserByID(contactID).setLatestContactDeletion(Instant.now());
// Notify the removed contact if online
if (connectionManager.isOnline(contactID))
writeProxy.write(connectionManager.getSocketID(contactID), new ContactOperation(sender.toCommon(), ElementOperation.REMOVE));
} else {
// The sender wants to be removed from a Group
final var group = persistenceManager.getGroupByID(contactID);
// The group has no more members and hence will be deleted
if (group.getContacts().isEmpty()) persistenceManager.deleteContact(group);
else {
// Informing the other members
writeProxy.writeToOnlineContacts(group.getContacts(), new ContactOperation(sender.toCommon(), ElementOperation.REMOVE));
group.getContacts().forEach(c -> ((User) c).setLatestContactDeletion(Instant.now()));
}
}
break; break;
default: default:
logger.warning(String.format("Received %s with an unsupported operation.", evt)); logger.warning(String.format("Received %s with an unsupported operation.", evt));

View File

@ -13,6 +13,7 @@ import javax.persistence.NoResultException;
import envoy.data.LoginCredentials; import envoy.data.LoginCredentials;
import envoy.event.*; import envoy.event.*;
import envoy.event.contact.ContactDeletionSinceLastLogin;
import envoy.server.data.*; import envoy.server.data.*;
import envoy.server.net.*; import envoy.server.net.*;
import envoy.server.util.*; import envoy.server.util.*;
@ -104,6 +105,7 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
user.setStatus(ONLINE); user.setStatus(ONLINE);
user.setPasswordHash(PasswordUtil.hash(credentials.getPassword())); user.setPasswordHash(PasswordUtil.hash(credentials.getPassword()));
user.setContacts(new HashSet<>()); user.setContacts(new HashSet<>());
user.setLatestContactDeletion(Instant.EPOCH);
persistenceManager.addContact(user); persistenceManager.addContact(user);
logger.info("Registered new " + user); logger.info("Registered new " + user);
} }
@ -208,5 +210,8 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
// Complete the handshake // Complete the handshake
writeProxy.write(socketID, user.toCommon()); writeProxy.write(socketID, user.toCommon());
// Notify the user if a contact deletion has happened since he last logged in
if (user.getLatestContactDeletion().isAfter(user.getLastSeen())) writeProxy.write(socketID, new ContactDeletionSinceLastLogin());
} }
} }