Add server side contact deletion
This commit is contained in:
parent
ab2e9aa114
commit
f97d61e58d
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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">
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -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)
|
||||||
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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user