Add Local Account Deletion #108
@@ -22,20 +22,21 @@ import envoy.client.net.WriteProxy;
 | 
			
		||||
 */
 | 
			
		||||
public class Chat implements Serializable {
 | 
			
		||||
 | 
			
		||||
	protected transient ObservableList<Message> messages = FXCollections.observableArrayList();
 | 
			
		||||
 | 
			
		||||
	protected final Contact recipient;
 | 
			
		||||
 | 
			
		||||
	protected boolean	disabled;
 | 
			
		||||
	protected boolean	underlyingContactDeleted;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Stores the last time an {@link envoy.event.IsTyping} event has been sent.
 | 
			
		||||
	 */
 | 
			
		||||
	protected transient long lastWritingEvent;
 | 
			
		||||
 | 
			
		||||
	protected transient ObservableList<Message> messages = FXCollections.observableArrayList();
 | 
			
		||||
 | 
			
		||||
	protected int						unreadAmount;
 | 
			
		||||
	protected static IntegerProperty	totalUnreadAmount	= new SimpleIntegerProperty();
 | 
			
		||||
 | 
			
		||||
	protected final Contact recipient;
 | 
			
		||||
 | 
			
		||||
	private static final long serialVersionUID = 2L;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
 
 | 
			
		||||
@@ -248,6 +248,10 @@ public final class LocalDB implements EventListener {
 | 
			
		||||
	 */
 | 
			
		||||
	@Event(eventType = EnvoyCloseEvent.class, priority = 500)
 | 
			
		||||
	private synchronized void save() {
 | 
			
		||||
 | 
			
		||||
		// Stop saving if this account has been deleted
 | 
			
		||||
		if (userFile == null)
 | 
			
		||||
			return;
 | 
			
		||||
		EnvoyLog.getLogger(LocalDB.class).log(Level.FINER, "Saving local database...");
 | 
			
		||||
 | 
			
		||||
		// Save users
 | 
			
		||||
@@ -273,6 +277,29 @@ public final class LocalDB implements EventListener {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Deletes any local remnant of this user.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @since Envoy Client v0.3-beta
 | 
			
		||||
	 */
 | 
			
		||||
	public void delete() {
 | 
			
		||||
		try {
 | 
			
		||||
 | 
			
		||||
			// Save ID generator - can be used for other users in that db
 | 
			
		||||
			if (hasIDGenerator())
 | 
			
		||||
				SerializationUtils.write(idGeneratorFile, idGenerator);
 | 
			
		||||
		} catch (final IOException e) {
 | 
			
		||||
			EnvoyLog.getLogger(LocalDB.class).log(Level.SEVERE, "Unable to save local database: ",
 | 
			
		||||
				e);
 | 
			
		||||
		}
 | 
			
		||||
		if (lastLoginFile != null)
 | 
			
		||||
			lastLoginFile.delete();
 | 
			
		||||
		userFile.delete();
 | 
			
		||||
		users.remove(user.getName());
 | 
			
		||||
		userFile = null;
 | 
			
		||||
		onLogout();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Event(priority = 500)
 | 
			
		||||
	private void onMessage(Message msg) {
 | 
			
		||||
		if (msg.getStatus() == MessageStatus.SENT)
 | 
			
		||||
@@ -404,6 +431,14 @@ public final class LocalDB implements EventListener {
 | 
			
		||||
		getChat(event.get().getID()).ifPresent(chat -> chat.setDisabled(true));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Event(priority = 500)
 | 
			
		||||
	private void onAccountDeletion(AccountDeletion deletion) {
 | 
			
		||||
		if (user.getID() == deletion.get())
 | 
			
		||||
			logger.log(Level.WARNING,
 | 
			
		||||
				"I have been informed by the server that I have been deleted without even knowing it...");
 | 
			
		||||
		getChat(deletion.get()).ifPresent(chat -> chat.setDisabled(true));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return a {@code Map<String, User>} of all users stored locally with their user names as keys
 | 
			
		||||
	 * @since Envoy Client v0.2-alpha
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								client/src/main/java/envoy/client/event/AccountDeletion.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								client/src/main/java/envoy/client/event/AccountDeletion.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
package envoy.client.event;
 | 
			
		||||
 | 
			
		||||
import envoy.event.Event;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Signifies the deletion of an account.
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Leon Hofmeister
 | 
			
		||||
 * @since Envoy Common v0.3-beta
 | 
			
		||||
 */
 | 
			
		||||
public class AccountDeletion extends Event<Long> {
 | 
			
		||||
 | 
			
		||||
	private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @param value the ID of the contact that was deleted
 | 
			
		||||
	 * @since Envoy Common v0.3-beta
 | 
			
		||||
	 */
 | 
			
		||||
	public AccountDeletion(Long value) {
 | 
			
		||||
		super(value);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,7 @@ import java.io.*;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.time.format.DateTimeFormatter;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.logging.*;
 | 
			
		||||
 | 
			
		||||
import javafx.animation.RotateTransition;
 | 
			
		||||
@@ -31,12 +32,13 @@ import envoy.data.*;
 | 
			
		||||
import envoy.data.Attachment.AttachmentType;
 | 
			
		||||
import envoy.data.Message.MessageStatus;
 | 
			
		||||
import envoy.event.*;
 | 
			
		||||
import envoy.event.contact.UserOperation;
 | 
			
		||||
import envoy.event.contact.*;
 | 
			
		||||
import envoy.exception.EnvoyException;
 | 
			
		||||
import envoy.util.EnvoyLog;
 | 
			
		||||
 | 
			
		||||
import envoy.client.data.*;
 | 
			
		||||
import envoy.client.data.audio.AudioRecorder;
 | 
			
		||||
import envoy.client.data.shortcuts.KeyboardMapping;
 | 
			
		||||
import envoy.client.event.*;
 | 
			
		||||
import envoy.client.net.*;
 | 
			
		||||
import envoy.client.ui.*;
 | 
			
		||||
@@ -51,7 +53,7 @@ import envoy.client.util.*;
 | 
			
		||||
 * @author Kai S. K. Engelbart
 | 
			
		||||
 * @since Envoy Client v0.1-beta
 | 
			
		||||
 */
 | 
			
		||||
public final class ChatScene implements EventListener, Restorable {
 | 
			
		||||
public final class ChatScene implements EventListener, Restorable, KeyboardMapping {
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
	private ListView<Message> messageList;
 | 
			
		||||
@@ -346,6 +348,11 @@ public final class ChatScene implements EventListener, Restorable {
 | 
			
		||||
		eventBus.removeListener(this);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Event(eventType = AccountDeletion.class)
 | 
			
		||||
	private void onAccountDeletion() {
 | 
			
		||||
		Platform.runLater(chatList::refresh);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onRestore() {
 | 
			
		||||
		updateRemainingCharsLabel();
 | 
			
		||||
@@ -871,4 +878,25 @@ public final class ChatScene implements EventListener, Restorable {
 | 
			
		||||
			: c -> c.getRecipient().getName().toLowerCase()
 | 
			
		||||
				.contains(contactSearch.getText().toLowerCase()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Map<KeyCombination, Runnable> getKeyboardShortcuts() {
 | 
			
		||||
		return Map.<KeyCombination, Runnable>of(
 | 
			
		||||
 | 
			
		||||
			// Delete text before the caret with "Control" + U
 | 
			
		||||
			new KeyCodeCombination(KeyCode.U, KeyCombination.CONTROL_DOWN), () -> {
 | 
			
		||||
				messageTextArea
 | 
			
		||||
					.setText(
 | 
			
		||||
						messageTextArea.getText().substring(messageTextArea.getCaretPosition()));
 | 
			
		||||
				checkPostConditions(false);
 | 
			
		||||
 | 
			
		||||
				// Delete text after the caret with "Control" + K
 | 
			
		||||
			}, new KeyCodeCombination(KeyCode.K, KeyCombination.CONTROL_DOWN), () -> {
 | 
			
		||||
				messageTextArea
 | 
			
		||||
					.setText(
 | 
			
		||||
						messageTextArea.getText().substring(0, messageTextArea.getCaretPosition()));
 | 
			
		||||
				checkPostConditions(false);
 | 
			
		||||
				messageTextArea.positionCaret(messageTextArea.getText().length());
 | 
			
		||||
			});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,11 +14,11 @@ import dev.kske.eventbus.*;
 | 
			
		||||
 | 
			
		||||
import envoy.data.*;
 | 
			
		||||
import envoy.event.GroupCreation;
 | 
			
		||||
import envoy.event.contact.UserOperation;
 | 
			
		||||
import envoy.event.contact.*;
 | 
			
		||||
import envoy.util.Bounds;
 | 
			
		||||
 | 
			
		||||
import envoy.client.data.*;
 | 
			
		||||
import envoy.client.event.BackEvent;
 | 
			
		||||
import envoy.client.event.*;
 | 
			
		||||
import envoy.client.ui.control.*;
 | 
			
		||||
import envoy.client.ui.listcell.ListCellFactory;
 | 
			
		||||
 | 
			
		||||
@@ -252,4 +252,10 @@ public class GroupCreationTab implements EventListener {
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Event
 | 
			
		||||
	private void onAccountDeletion(AccountDeletion deletion) {
 | 
			
		||||
		final var deletedID = deletion.get();
 | 
			
		||||
		Platform.runLater(() -> userList.getItems().removeIf(user -> (user.getID() == deletedID)));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ import javafx.scene.image.*;
 | 
			
		||||
import javafx.scene.input.InputEvent;
 | 
			
		||||
import javafx.scene.layout.HBox;
 | 
			
		||||
import javafx.stage.FileChooser;
 | 
			
		||||
import javafx.util.Duration;
 | 
			
		||||
 | 
			
		||||
import dev.kske.eventbus.EventBus;
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +21,7 @@ import envoy.event.*;
 | 
			
		||||
import envoy.util.*;
 | 
			
		||||
 | 
			
		||||
import envoy.client.ui.control.ProfilePicImageView;
 | 
			
		||||
import envoy.client.util.IconUtil;
 | 
			
		||||
import envoy.client.util.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Leon Hofmeister
 | 
			
		||||
@@ -38,6 +39,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
 | 
			
		||||
	private final PasswordField	newPasswordField		= new PasswordField();
 | 
			
		||||
	private final PasswordField	repeatNewPasswordField	= new PasswordField();
 | 
			
		||||
	private final Button		saveButton				= new Button("Save");
 | 
			
		||||
	private final Button		deleteAccountButton		= new Button("Delete Account (Locally)");
 | 
			
		||||
 | 
			
		||||
	private static final EventBus	eventBus	= EventBus.getInstance();
 | 
			
		||||
	private static final Logger		logger		= EnvoyLog.getLogger(UserSettingsPane.class);
 | 
			
		||||
@@ -112,15 +114,18 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
 | 
			
		||||
 | 
			
		||||
		final PasswordField[]					passwordFields	=
 | 
			
		||||
			{ currentPasswordField, newPasswordField, repeatNewPasswordField };
 | 
			
		||||
		final EventHandler<? super InputEvent>	passwordEntered	= e -> {
 | 
			
		||||
		final EventHandler<? super InputEvent>	passwordEntered	=
 | 
			
		||||
			e -> {
 | 
			
		||||
																		newPassword	=
 | 
			
		||||
																		newPasswordField.getText();
 | 
			
		||||
																	validPassword	= newPassword
 | 
			
		||||
																		.equals(
 | 
			
		||||
																			newPasswordField
 | 
			
		||||
																				.getText();
 | 
			
		||||
																		validPassword =
 | 
			
		||||
																			newPassword.equals(
 | 
			
		||||
																				repeatNewPasswordField
 | 
			
		||||
																					.getText())
 | 
			
		||||
																				&& !newPasswordField
 | 
			
		||||
																			.getText().isBlank();
 | 
			
		||||
																					.getText()
 | 
			
		||||
																					.isBlank();
 | 
			
		||||
																	};
 | 
			
		||||
		newPasswordField.setOnInputMethodTextChanged(passwordEntered);
 | 
			
		||||
		newPasswordField.setOnKeyTyped(passwordEntered);
 | 
			
		||||
@@ -140,6 +145,18 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
 | 
			
		||||
			.setOnAction(e -> save(currentPasswordField.getText()));
 | 
			
		||||
		saveButton.setAlignment(Pos.BOTTOM_RIGHT);
 | 
			
		||||
		getChildren().add(saveButton);
 | 
			
		||||
 | 
			
		||||
		// Displaying the delete account button
 | 
			
		||||
		deleteAccountButton.setAlignment(Pos.BASELINE_CENTER);
 | 
			
		||||
		deleteAccountButton.setOnAction(e -> UserUtil.deleteAccount());
 | 
			
		||||
		deleteAccountButton.setText("Delete Account (locally)");
 | 
			
		||||
		deleteAccountButton.setPrefHeight(25);
 | 
			
		||||
		deleteAccountButton.getStyleClass().clear();
 | 
			
		||||
		deleteAccountButton.getStyleClass().add("danger-button");
 | 
			
		||||
		final var tooltip = new Tooltip("Remote deletion is currently unsupported.");
 | 
			
		||||
		tooltip.setShowDelay(Duration.millis(100));
 | 
			
		||||
		deleteAccountButton.setTooltip(tooltip);
 | 
			
		||||
		getChildren().add(deleteAccountButton);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ package envoy.client.util;
 | 
			
		||||
 | 
			
		||||
import java.util.logging.*;
 | 
			
		||||
 | 
			
		||||
import javafx.scene.control.Alert;
 | 
			
		||||
import javafx.scene.control.*;
 | 
			
		||||
import javafx.scene.control.Alert.AlertType;
 | 
			
		||||
 | 
			
		||||
import dev.kske.eventbus.EventBus;
 | 
			
		||||
@@ -121,4 +121,35 @@ public final class UserUtil {
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Deletes anything pointing to this user, independent of client or server. Will do nothing if
 | 
			
		||||
	 * the client is currently offline.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @since Envoy Client v0.3-beta
 | 
			
		||||
	 */
 | 
			
		||||
	public static void deleteAccount() {
 | 
			
		||||
 | 
			
		||||
		// Show the first wall of defense, if not disabled by the user
 | 
			
		||||
		final var outerAlert = new Alert(AlertType.CONFIRMATION);
 | 
			
		||||
		outerAlert.setContentText(
 | 
			
		||||
			"Are you sure you want to delete your account entirely? This action can seriously not be undone.");
 | 
			
		||||
		outerAlert.setTitle("Delete Account?");
 | 
			
		||||
		AlertHelper.confirmAction(outerAlert, () -> {
 | 
			
		||||
 | 
			
		||||
			// Show the final wall of defense in every case
 | 
			
		||||
			final var lastAlert = new Alert(AlertType.WARNING,
 | 
			
		||||
				"Do you REALLY want to delete your account? Last Warning. Proceed?",
 | 
			
		||||
				ButtonType.CANCEL, ButtonType.OK);
 | 
			
		||||
			lastAlert.setTitle("Delete Account?");
 | 
			
		||||
			lastAlert.showAndWait().filter(ButtonType.OK::equals).ifPresent(b2 -> {
 | 
			
		||||
 | 
			
		||||
				// Delete the account
 | 
			
		||||
				// TODO: Notify server of account deletion
 | 
			
		||||
				context.getLocalDB().delete();
 | 
			
		||||
				logger.log(Level.INFO, "The user just deleted his account. Goodbye.");
 | 
			
		||||
				ShutdownHelper.exit(true);
 | 
			
		||||
			});
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -70,6 +70,17 @@
 | 
			
		||||
	-fx-text-fill: gray;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.danger-button {
 | 
			
		||||
	-fx-background-color: red;
 | 
			
		||||
	-fx-text-fill: white;
 | 
			
		||||
	-fx-background-radius: 0.2em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.danger-button:hover {
 | 
			
		||||
	-fx-scale-x: 1.05;
 | 
			
		||||
	-fx-scale-y: 1.05;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.received-message {
 | 
			
		||||
	-fx-alignment: center-left;
 | 
			
		||||
	-fx-background-radius: 1.3em;
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,10 @@
 | 
			
		||||
	-fx-background-color: black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tooltip {
 | 
			
		||||
	-fx-text-fill: black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#login-input-field {
 | 
			
		||||
	-fx-border-color: black;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -121,8 +121,9 @@ public final class PersistenceManager {
 | 
			
		||||
		transaction(() -> {
 | 
			
		||||
 | 
			
		||||
			// Remove this contact from the contact list of his contacts
 | 
			
		||||
			for (final var remainingContact : contact.getContacts())
 | 
			
		||||
			for (final var remainingContact : contact.contacts)
 | 
			
		||||
				remainingContact.getContacts().remove(contact);
 | 
			
		||||
			contact.contacts.clear();
 | 
			
		||||
		});
 | 
			
		||||
		remove(contact);
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -49,8 +49,10 @@ public final class ConnectionManager implements ISocketIdListener {
 | 
			
		||||
			// Notify contacts of this users offline-going
 | 
			
		||||
			final envoy.server.data.User user =
 | 
			
		||||
				PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID));
 | 
			
		||||
			if (user != null) {
 | 
			
		||||
				user.setLastSeen(Instant.now());
 | 
			
		||||
				UserStatusChangeProcessor.updateUserStatus(user, UserStatus.OFFLINE);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Remove the socket
 | 
			
		||||
			sockets.entrySet().removeIf(e -> e.getValue() == socketID);
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,8 @@ public final class GroupMessageStatusChangeProcessor
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		GroupMessage gmsg = (GroupMessage) persistenceManager.getMessageByID(statusChange.getID());
 | 
			
		||||
		if (gmsg == null)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		// Any other status than READ is not supposed to be sent to the server
 | 
			
		||||
		if (statusChange.get() != MessageStatus.READ) {
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,10 @@ public final class GroupResizeProcessor implements ObjectProcessor<GroupResize>
 | 
			
		||||
		final var	group	= persistenceManager.getGroupByID(groupResize.getGroupID());
 | 
			
		||||
		final var	sender	= persistenceManager.getUserByID(groupResize.get().getID());
 | 
			
		||||
 | 
			
		||||
		// TODO: Inform the sender that this group has already been deleted
 | 
			
		||||
		if (group == null)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		// Perform the desired operation
 | 
			
		||||
		switch (groupResize.getOperation()) {
 | 
			
		||||
			case ADD:
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,8 @@ public final class MessageStatusChangeProcessor implements ObjectProcessor<Messa
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		final var msg = persistenceManager.getMessageByID(statusChange.getID());
 | 
			
		||||
		if (msg == null)
 | 
			
		||||
			return;
 | 
			
		||||
		msg.read();
 | 
			
		||||
		persistenceManager.updateMessage(msg);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -22,9 +22,15 @@ public final class UserOperationProcessor implements ObjectProcessor<UserOperati
 | 
			
		||||
	private static final PersistenceManager	persistenceManager	= PersistenceManager.getInstance();
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void process(UserOperation evt, long socketId, ObjectWriteProxy writeProxy) {
 | 
			
		||||
		final long	userID		= ConnectionManager.getInstance().getUserIDBySocketID(socketId);
 | 
			
		||||
	public void process(UserOperation evt, long socketID, ObjectWriteProxy writeProxy) {
 | 
			
		||||
		final long	userID		= ConnectionManager.getInstance().getUserIDBySocketID(socketID);
 | 
			
		||||
		final long	contactID	= evt.get().getID();
 | 
			
		||||
		final var	recipient	= persistenceManager.getUserByID(contactID);
 | 
			
		||||
 | 
			
		||||
		// TODO: Inform the sender if the requested contact has already been deleted
 | 
			
		||||
		if (recipient == null)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		final var sender = persistenceManager.getUserByID(userID);
 | 
			
		||||
		switch (evt.getOperationType()) {
 | 
			
		||||
			case ADD:
 | 
			
		||||
@@ -45,7 +51,7 @@ public final class UserOperationProcessor implements ObjectProcessor<UserOperati
 | 
			
		||||
				sender.setLatestContactDeletion(Instant.now());
 | 
			
		||||
 | 
			
		||||
				// Notify the removed contact on next startup(s) of this deletion
 | 
			
		||||
				persistenceManager.getUserByID(contactID).setLatestContactDeletion(Instant.now());
 | 
			
		||||
				recipient.setLatestContactDeletion(Instant.now());
 | 
			
		||||
 | 
			
		||||
				// Notify the removed contact if online
 | 
			
		||||
				if (connectionManager.isOnline(contactID))
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user