Add server side contact deletion
This commit is contained in:
		@@ -191,7 +191,6 @@ public final class ChatScene implements EventListener, Restorable {
 | 
			
		||||
 | 
			
		||||
		// Set the design of the box in the upper-left corner
 | 
			
		||||
		settingsButton.setAlignment(Pos.BOTTOM_RIGHT);
 | 
			
		||||
		HBox.setHgrow(spaceBetweenUserAndSettingsButton, Priority.ALWAYS);
 | 
			
		||||
		generateOwnStatusControl();
 | 
			
		||||
 | 
			
		||||
		Platform.runLater(() -> {
 | 
			
		||||
@@ -727,6 +726,7 @@ public final class ChatScene implements EventListener, Restorable {
 | 
			
		||||
			// Else prepend it to the HBox children
 | 
			
		||||
			final var ownUserControl = new ContactControl(localDB.getUser());
 | 
			
		||||
			ownUserControl.setAlignment(Pos.CENTER_LEFT);
 | 
			
		||||
			HBox.setHgrow(ownUserControl, Priority.NEVER);
 | 
			
		||||
			ownContactControl.getChildren().add(0, ownUserControl);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -167,7 +167,7 @@
 | 
			
		||||
				<HBox id="transparent-background" fx:id="ownContactControl">
 | 
			
		||||
					<children>
 | 
			
		||||
						<Region id="transparent-background" prefWidth="120"
 | 
			
		||||
							fx:id="spaceBetweenUserAndSettingsButton" />
 | 
			
		||||
							fx:id="spaceBetweenUserAndSettingsButton" HBox.hgrow="ALWAYS" />
 | 
			
		||||
						<Button fx:id="settingsButton" mnemonicParsing="false"
 | 
			
		||||
							onAction="#settingsButtonClicked" prefHeight="30.0"
 | 
			
		||||
							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();
 | 
			
		||||
		}
 | 
			
		||||
		try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
 | 
			
		||||
			for (var obj : objs)
 | 
			
		||||
			for (final var obj : objs)
 | 
			
		||||
				out.writeObject(obj);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -119,7 +119,7 @@ public final class SerializationUtils {
 | 
			
		||||
	 * @since Envoy Common v0.2-alpha
 | 
			
		||||
	 */
 | 
			
		||||
	public static byte[] writeToByteArray(Object obj) throws IOException {
 | 
			
		||||
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
 | 
			
		||||
		final var baos = new ByteArrayOutputStream();
 | 
			
		||||
		try (ObjectOutputStream oout = new ObjectOutputStream(baos)) {
 | 
			
		||||
			oout.writeObject(obj);
 | 
			
		||||
		}
 | 
			
		||||
@@ -137,10 +137,10 @@ public final class SerializationUtils {
 | 
			
		||||
	 */
 | 
			
		||||
	public static void writeBytesWithLength(Object obj, OutputStream out) throws IOException {
 | 
			
		||||
		// Serialize object to byte array
 | 
			
		||||
		byte[] objBytes = writeToByteArray(obj);
 | 
			
		||||
		final var objBytes = writeToByteArray(obj);
 | 
			
		||||
 | 
			
		||||
		// Get length of byte array in bytes
 | 
			
		||||
		byte[] objLen = intToBytes(objBytes.length);
 | 
			
		||||
		final var objLen = intToBytes(objBytes.length);
 | 
			
		||||
 | 
			
		||||
		// Write length and byte array
 | 
			
		||||
		out.write(objLen);
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ public class Message {
 | 
			
		||||
	@JoinColumn
 | 
			
		||||
	protected User sender;
 | 
			
		||||
 | 
			
		||||
	@ManyToOne
 | 
			
		||||
	@ManyToOne(cascade = CascadeType.REMOVE)
 | 
			
		||||
	@JoinColumn
 | 
			
		||||
	protected Contact recipient;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -97,7 +97,20 @@ public final class PersistenceManager {
 | 
			
		||||
	 * @param contact the {@link Contact} to delete
 | 
			
		||||
	 * @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.
 | 
			
		||||
@@ -232,7 +245,7 @@ public final class PersistenceManager {
 | 
			
		||||
		final var	c1	= getContactByID(contactID1);
 | 
			
		||||
		final var	c2	= getContactByID(contactID2);
 | 
			
		||||
 | 
			
		||||
		// Add users to each others contact lists
 | 
			
		||||
		// Add users to each others contact list
 | 
			
		||||
		c1.getContacts().add(c2);
 | 
			
		||||
		c2.getContacts().add(c1);
 | 
			
		||||
 | 
			
		||||
@@ -240,6 +253,27 @@ public final class PersistenceManager {
 | 
			
		||||
		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
 | 
			
		||||
	 * @return the contacts of this User
 | 
			
		||||
 
 | 
			
		||||
@@ -70,6 +70,9 @@ public final class User extends Contact {
 | 
			
		||||
	@Column(name = "last_seen")
 | 
			
		||||
	private Instant lastSeen;
 | 
			
		||||
 | 
			
		||||
	@Column(name = "latest_contact_deletion")
 | 
			
		||||
	private Instant latestContactDeletion;
 | 
			
		||||
 | 
			
		||||
	private UserStatus status;
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
@@ -140,4 +143,16 @@ public final class User extends Contact {
 | 
			
		||||
	 * @since Envoy Server Standalone v0.1-alpha
 | 
			
		||||
	 */
 | 
			
		||||
	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;
 | 
			
		||||
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
import java.time.Instant;
 | 
			
		||||
import java.util.logging.*;
 | 
			
		||||
 | 
			
		||||
import envoy.event.ElementOperation;
 | 
			
		||||
import envoy.event.contact.ContactOperation;
 | 
			
		||||
import envoy.server.data.PersistenceManager;
 | 
			
		||||
import envoy.server.data.*;
 | 
			
		||||
import envoy.server.net.*;
 | 
			
		||||
import envoy.util.EnvoyLog;
 | 
			
		||||
 | 
			
		||||
@@ -14,23 +15,55 @@ import envoy.util.EnvoyLog;
 | 
			
		||||
 */
 | 
			
		||||
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 PersistenceManager	persistenceManager	= PersistenceManager.getInstance();
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	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()) {
 | 
			
		||||
			case ADD:
 | 
			
		||||
				final long userID = ConnectionManager.getInstance().getUserIDBySocketID(socketId);
 | 
			
		||||
				final long contactId = evt.get().getID();
 | 
			
		||||
 | 
			
		||||
				logger.fine(String.format("Adding user %s to the contact list of user %d.%n", evt.get(), userID));
 | 
			
		||||
				PersistenceManager.getInstance().addContactBidirectional(userID, contactId);
 | 
			
		||||
				logger.log(Level.FINE, String.format("Adding %s to the contact list of user %d.", evt.get(), userID));
 | 
			
		||||
				persistenceManager.addContactBidirectional(userID, contactID);
 | 
			
		||||
 | 
			
		||||
				// Notify the contact if online
 | 
			
		||||
				if (ConnectionManager.getInstance().isOnline(contactId))
 | 
			
		||||
					writeProxy.write(connectionManager.getSocketID(contactId),
 | 
			
		||||
							new ContactOperation(PersistenceManager.getInstance().getUserByID(userID).toCommon(), ElementOperation.ADD));
 | 
			
		||||
				if (connectionManager.isOnline(contactID))
 | 
			
		||||
					writeProxy.write(connectionManager.getSocketID(contactID), new ContactOperation(sender.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;
 | 
			
		||||
			default:
 | 
			
		||||
				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.event.*;
 | 
			
		||||
import envoy.event.contact.ContactDeletionSinceLastLogin;
 | 
			
		||||
import envoy.server.data.*;
 | 
			
		||||
import envoy.server.net.*;
 | 
			
		||||
import envoy.server.util.*;
 | 
			
		||||
@@ -104,6 +105,7 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
 | 
			
		||||
				user.setStatus(ONLINE);
 | 
			
		||||
				user.setPasswordHash(PasswordUtil.hash(credentials.getPassword()));
 | 
			
		||||
				user.setContacts(new HashSet<>());
 | 
			
		||||
				user.setLatestContactDeletion(Instant.EPOCH);
 | 
			
		||||
				persistenceManager.addContact(user);
 | 
			
		||||
				logger.info("Registered new " + user);
 | 
			
		||||
			}
 | 
			
		||||
@@ -208,5 +210,8 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
 | 
			
		||||
 | 
			
		||||
		// Complete the handshake
 | 
			
		||||
		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