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