Add Local Account Deletion #108
@@ -283,6 +283,15 @@ public final class LocalDB implements EventListener {
 | 
			
		||||
	 * @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();
 | 
			
		||||
@@ -427,10 +436,7 @@ public final class LocalDB implements EventListener {
 | 
			
		||||
		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);
 | 
			
		||||
			chat.setUnderlyingContactDeleted(true);
 | 
			
		||||
		});
 | 
			
		||||
		getChat(deletion.get()).ifPresent(chat -> chat.setDisabled(true));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
package envoy.event.contact;
 | 
			
		||||
package envoy.client.event;
 | 
			
		||||
 | 
			
		||||
import envoy.event.Event;
 | 
			
		||||
 | 
			
		||||
@@ -18,7 +18,7 @@ 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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
@@ -38,7 +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");
 | 
			
		||||
	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);
 | 
			
		||||
@@ -148,6 +149,13 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
 | 
			
		||||
		// 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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import dev.kske.eventbus.EventBus;
 | 
			
		||||
import envoy.data.*;
 | 
			
		||||
import envoy.data.User.UserStatus;
 | 
			
		||||
import envoy.event.*;
 | 
			
		||||
import envoy.event.contact.*;
 | 
			
		||||
import envoy.event.contact.UserOperation;
 | 
			
		||||
import envoy.util.EnvoyLog;
 | 
			
		||||
 | 
			
		||||
import envoy.client.data.Context;
 | 
			
		||||
@@ -129,32 +129,27 @@ public final class UserUtil {
 | 
			
		||||
	 * @since Envoy Client v0.3-beta
 | 
			
		||||
	 */
 | 
			
		||||
	public static void deleteAccount() {
 | 
			
		||||
		if (!context.getClient().isOnline())
 | 
			
		||||
			return;
 | 
			
		||||
		else {
 | 
			
		||||
 | 
			
		||||
			// 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 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 -> {
 | 
			
		||||
			// 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
 | 
			
		||||
					context.getClient()
 | 
			
		||||
						.send(new AccountDeletion(context.getLocalDB().getUser().getID()));
 | 
			
		||||
					context.getLocalDB().delete();
 | 
			
		||||
					logger.log(Level.INFO, "The user just deleted his account. Goodbye.");
 | 
			
		||||
					ShutdownHelper.exit(true);
 | 
			
		||||
				});
 | 
			
		||||
				// 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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -59,8 +59,7 @@ public final class Startup {
 | 
			
		||||
				new NameChangeProcessor(),
 | 
			
		||||
				new ProfilePicChangeProcessor(),
 | 
			
		||||
				new PasswordChangeRequestProcessor(),
 | 
			
		||||
				new IssueProposalProcessor(),
 | 
			
		||||
				new AccountDeletionProcessor())));
 | 
			
		||||
				new IssueProposalProcessor())));
 | 
			
		||||
 | 
			
		||||
		// Initialize the current message ID
 | 
			
		||||
		final var persistenceManager = PersistenceManager.getInstance();
 | 
			
		||||
 
 | 
			
		||||
@@ -27,18 +27,14 @@ import envoy.data.Message.MessageStatus;
 | 
			
		||||
@Entity
 | 
			
		||||
@Table(name = "messages")
 | 
			
		||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
 | 
			
		||||
@NamedQueries({
 | 
			
		||||
				@NamedQuery(name = Message.getPending, query = "SELECT m FROM Message m WHERE "
 | 
			
		||||
					// Send to or by the user before last seen
 | 
			
		||||
					+ "(m.sender = :user OR m.recipient = :user) AND m.creationDate > :lastSeen "
 | 
			
		||||
					// SENT to the user
 | 
			
		||||
					+ "OR m.recipient = :user AND m.status = envoy.data.Message$MessageStatus.SENT "
 | 
			
		||||
					// Sent by the user and RECEIVED / READ after last seen
 | 
			
		||||
					+ "OR m.sender = :user AND (m.status = envoy.data.Message$MessageStatus.RECEIVED AND m.receivedDate > :lastSeen "
 | 
			
		||||
					+ "OR m.status = envoy.data.Message$MessageStatus.READ AND m.readDate > :lastSeen)"),
 | 
			
		||||
				@NamedQuery(name = Message.deleteByRecipient, query = "DELETE FROM Message m WHERE m.recipient = :deleted OR m.sender = :deleted")
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
@NamedQuery(name = Message.getPending, query = "SELECT m FROM Message m WHERE "
 | 
			
		||||
	// Send to or by the user before last seen
 | 
			
		||||
	+ "(m.sender = :user OR m.recipient = :user) AND m.creationDate > :lastSeen "
 | 
			
		||||
	// SENT to the user
 | 
			
		||||
	+ "OR m.recipient = :user AND m.status = envoy.data.Message$MessageStatus.SENT "
 | 
			
		||||
	// Sent by the user and RECEIVED / READ after last seen
 | 
			
		||||
	+ "OR m.sender = :user AND (m.status = envoy.data.Message$MessageStatus.RECEIVED AND m.receivedDate > :lastSeen "
 | 
			
		||||
	+ "OR m.status = envoy.data.Message$MessageStatus.READ AND m.readDate > :lastSeen)")
 | 
			
		||||
public class Message {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -49,13 +45,6 @@ public class Message {
 | 
			
		||||
	 */
 | 
			
		||||
	public static final String getPending = "Message.getPending";
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Named query deleting all messages of a user (parameter {@code :deleted}).
 | 
			
		||||
	 *
 | 
			
		||||
	 * @since Envoy Server v0.3-beta
 | 
			
		||||
	 */
 | 
			
		||||
	public static final String deleteByRecipient = "Message.deleteByRecipient";
 | 
			
		||||
 | 
			
		||||
	@Id
 | 
			
		||||
	protected long id;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -121,12 +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);
 | 
			
		||||
 | 
			
		||||
			entityManager
 | 
			
		||||
				.createNamedQuery(Message.deleteByRecipient).setParameter("deleted", contact)
 | 
			
		||||
				.executeUpdate();
 | 
			
		||||
			contact.contacts.clear();
 | 
			
		||||
		});
 | 
			
		||||
		remove(contact);
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
package envoy.server.processors;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.time.Instant;
 | 
			
		||||
 | 
			
		||||
import envoy.event.contact.AccountDeletion;
 | 
			
		||||
 | 
			
		||||
import envoy.server.data.*;
 | 
			
		||||
import envoy.server.net.ObjectWriteProxy;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Leon Hofmeister
 | 
			
		||||
 * @since Envoy Server v0.3-beta
 | 
			
		||||
 */
 | 
			
		||||
public class AccountDeletionProcessor implements ObjectProcessor<AccountDeletion> {
 | 
			
		||||
 | 
			
		||||
	private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void process(AccountDeletion input, long socketID, ObjectWriteProxy writeProxy)
 | 
			
		||||
		throws IOException {
 | 
			
		||||
		final var contact = persistenceManager.getContactByID(input.get());
 | 
			
		||||
 | 
			
		||||
		contact.getContacts().forEach(c -> {
 | 
			
		||||
			persistenceManager.removeContactBidirectional(contact, c);
 | 
			
		||||
			if (c instanceof User)
 | 
			
		||||
				((User) c).setLatestContactDeletion(Instant.now());
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		writeProxy.writeToOnlineContacts(contact.getContacts(), input);
 | 
			
		||||
		persistenceManager.deleteContact(contact);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ import java.time.Instant;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
 | 
			
		||||
import envoy.event.GroupResize;
 | 
			
		||||
import envoy.event.contact.AccountDeletion;
 | 
			
		||||
import envoy.util.EnvoyLog;
 | 
			
		||||
 | 
			
		||||
import envoy.server.data.*;
 | 
			
		||||
@@ -25,11 +24,9 @@ public final class GroupResizeProcessor implements ObjectProcessor<GroupResize>
 | 
			
		||||
		final var	group	= persistenceManager.getGroupByID(groupResize.getGroupID());
 | 
			
		||||
		final var	sender	= persistenceManager.getUserByID(groupResize.get().getID());
 | 
			
		||||
 | 
			
		||||
		// Inform the sender that this group has already been deleted
 | 
			
		||||
		if (group == null) {
 | 
			
		||||
			writeProxy.write(socketID, new AccountDeletion(groupResize.getGroupID()));
 | 
			
		||||
		// TODO: Inform the sender that this group has already been deleted
 | 
			
		||||
		if (group == null)
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Perform the desired operation
 | 
			
		||||
		switch (groupResize.getOperation()) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ import java.time.Instant;
 | 
			
		||||
import java.util.logging.*;
 | 
			
		||||
 | 
			
		||||
import envoy.event.ElementOperation;
 | 
			
		||||
import envoy.event.contact.*;
 | 
			
		||||
import envoy.event.contact.UserOperation;
 | 
			
		||||
import envoy.util.EnvoyLog;
 | 
			
		||||
 | 
			
		||||
import envoy.server.data.PersistenceManager;
 | 
			
		||||
@@ -27,11 +27,9 @@ public final class UserOperationProcessor implements ObjectProcessor<UserOperati
 | 
			
		||||
		final long	contactID	= evt.get().getID();
 | 
			
		||||
		final var	recipient	= persistenceManager.getUserByID(contactID);
 | 
			
		||||
 | 
			
		||||
		// Inform the sender if the requested contact has already been deleted
 | 
			
		||||
		if (recipient == null) {
 | 
			
		||||
			writeProxy.write(socketID, new AccountDeletion(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()) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user