Added ability to change user status

This commit is contained in:
Leon Hofmeister 2020-10-07 23:43:30 +02:00
parent f2eb89d469
commit 637ad9f61f
Signed by: delvh
GPG Key ID: 3DECE05F6D9A647C
11 changed files with 81 additions and 81 deletions

View File

@ -297,8 +297,9 @@ public final class ChatScene implements EventListener, Restorable {
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new)); chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
messageList.setCellFactory(MessageListCell::new); messageList.setCellFactory(MessageListCell::new);
// TODO: cache image // TODO: cache image
if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43)); if (currentChat != null)
else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43)); if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
} }
@Event(eventType = Logout.class, priority = 200) @Event(eventType = Logout.class, priority = 200)

View File

@ -5,8 +5,6 @@ import javafx.scene.control.*;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.stage.DirectoryChooser; import javafx.stage.DirectoryChooser;
import envoy.client.data.Context;
/** /**
* Displays options for downloading {@link envoy.data.Attachment}s. * Displays options for downloading {@link envoy.data.Attachment}s.
* *
@ -47,7 +45,7 @@ public final class DownloadSettingsPane extends SettingsPane {
final var directoryChooser = new DirectoryChooser(); final var directoryChooser = new DirectoryChooser();
directoryChooser.setTitle("Select the directory where attachments should be saved to"); directoryChooser.setTitle("Select the directory where attachments should be saved to");
directoryChooser.setInitialDirectory(settings.getDownloadLocation()); directoryChooser.setInitialDirectory(settings.getDownloadLocation());
final var selectedDirectory = directoryChooser.showDialog(Context.getInstance().getSceneContext().getStage()); final var selectedDirectory = directoryChooser.showDialog(context.getSceneContext().getStage());
if (selectedDirectory != null) { if (selectedDirectory != null) {
currentPath.setText(selectedDirectory.getAbsolutePath()); currentPath.setText(selectedDirectory.getAbsolutePath());

View File

@ -7,6 +7,7 @@ import envoy.client.event.ThemeChangeEvent;
import envoy.client.helper.ShutdownHelper; import envoy.client.helper.ShutdownHelper;
import envoy.client.ui.StatusTrayIcon; import envoy.client.ui.StatusTrayIcon;
import envoy.data.User.UserStatus; import envoy.data.User.UserStatus;
import envoy.event.UserStatusChange;
import dev.kske.eventbus.EventBus; import dev.kske.eventbus.EventBus;
@ -55,13 +56,23 @@ public final class GeneralSettingsPane extends SettingsPane {
combobox.setOnAction(e -> { settings.setCurrentTheme(combobox.getValue()); EventBus.getInstance().dispatch(new ThemeChangeEvent()); }); combobox.setOnAction(e -> { settings.setCurrentTheme(combobox.getValue()); EventBus.getInstance().dispatch(new ThemeChangeEvent()); });
getChildren().add(combobox); getChildren().add(combobox);
final var statusComboBox = new ComboBox<UserStatus>(); if (context.getClient().isOnline()) {
statusComboBox.getItems().setAll(UserStatus.values()); final var statusComboBox = new ComboBox<UserStatus>();
statusComboBox.setValue(UserStatus.ONLINE); statusComboBox.getItems().setAll(UserStatus.values());
statusComboBox.setTooltip(new Tooltip("Change your current status")); statusComboBox.setValue(context.getLocalDB().getUser().getStatus());
// TODO add action when value is changed statusComboBox.setTooltip(new Tooltip("Change your current status"));
statusComboBox.setOnAction(e -> {}); statusComboBox.setOnAction(e -> {
getChildren().add(statusComboBox); final var status = statusComboBox.getValue();
if (status == null) return;
else {
final var user = context.getLocalDB().getUser();
user.setStatus(status);
// TODO update status in ChatScene
context.getClient().send(new UserStatusChange(user.getID(), status));
}
});
getChildren().add(statusComboBox);
}
final var logoutButton = new Button("Logout"); final var logoutButton = new Button("Logout");
logoutButton.setOnAction(e -> ShutdownHelper.logout()); logoutButton.setOnAction(e -> ShutdownHelper.logout());

View File

@ -5,7 +5,6 @@ import javafx.scene.control.*;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import envoy.client.data.Context;
import envoy.client.net.Client; import envoy.client.net.Client;
/** /**
@ -20,7 +19,7 @@ import envoy.client.net.Client;
*/ */
public abstract class OnlineOnlySettingsPane extends SettingsPane { public abstract class OnlineOnlySettingsPane extends SettingsPane {
protected final Client client = Context.getInstance().getClient(); protected final Client client = context.getClient();
private final Tooltip beOnlineReminder = new Tooltip("You need to be online to modify your account."); private final Tooltip beOnlineReminder = new Tooltip("You need to be online to modify your account.");

View File

@ -2,7 +2,7 @@ package envoy.client.ui.settings;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import envoy.client.data.Settings; import envoy.client.data.*;
/** /**
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
@ -12,7 +12,8 @@ public abstract class SettingsPane extends VBox {
protected String title; protected String title;
protected static final Settings settings = Settings.getInstance(); protected static final Settings settings = Settings.getInstance();
protected static final Context context = Context.getInstance();
protected SettingsPane(String title) { this.title = title; } protected SettingsPane(String title) { this.title = title; }

View File

@ -14,7 +14,6 @@ import javafx.scene.input.InputEvent;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import envoy.client.data.Context;
import envoy.client.ui.control.ProfilePicImageView; import envoy.client.ui.control.ProfilePicImageView;
import envoy.client.util.IconUtil; import envoy.client.util.IconUtil;
import envoy.event.*; import envoy.event.*;
@ -66,7 +65,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
pictureChooser.setInitialDirectory(new File(System.getProperty("user.home"))); pictureChooser.setInitialDirectory(new File(System.getProperty("user.home")));
pictureChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif")); pictureChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"));
final var file = pictureChooser.showOpenDialog(Context.getInstance().getSceneContext().getStage()); final var file = pictureChooser.showOpenDialog(context.getSceneContext().getStage());
if (file != null) { if (file != null) {

View File

@ -6,7 +6,7 @@
-fx-background-radius: 15.0px; -fx-background-radius: 15.0px;
} }
.list-cell:selected, .menu-item:hover { .list-cell:selected, .menu-item:hover, .combo-box-popup .list-view .list-cell:selected {
-fx-background-color: #454c4f; -fx-background-color: #454c4f;
} }

View File

@ -18,7 +18,7 @@
-fx-background-color: lightgray; -fx-background-color: lightgray;
} }
#message-list, .text-field, .password-field, .tooltip, .pane, .pane .content, .vbox, .titled-pane > .title, .titled-pane > *.content, .context-menu, .menu-item, #quick-select-list { #message-list, .text-field, .password-field, .tooltip, .pane, .pane .content, .vbox, .titled-pane > .title, .titled-pane > *.content, .context-menu, .menu-item, .combo-box-popup .list-view .list-cell, #quick-select-list {
-fx-background-color: #222222; -fx-background-color: #222222;
} }

View File

@ -22,14 +22,14 @@ public final class ConnectionManager implements ISocketIdListener {
* *
* @since Envoy Server Standalone v0.1-alpha * @since Envoy Server Standalone v0.1-alpha
*/ */
private Set<Long> pendingSockets = new HashSet<>(); private final Set<Long> pendingSockets = new HashSet<>();
/** /**
* Contains all socket IDs that have acquired a user ID as keys to these IDs. * Contains all socket IDs that have acquired a user ID as keys to these IDs.
* *
* @since Envoy Server Standalone v0.1-alpha * @since Envoy Server Standalone v0.1-alpha
*/ */
private Map<Long, Long> sockets = new HashMap<>(); private final Map<Long, Long> sockets = new HashMap<>();
private static ConnectionManager connectionManager = new ConnectionManager(); private static ConnectionManager connectionManager = new ConnectionManager();
@ -44,11 +44,11 @@ public final class ConnectionManager implements ISocketIdListener {
@Override @Override
public void socketCancelled(long socketID) { public void socketCancelled(long socketID) {
if (!pendingSockets.remove(socketID)) { if (!pendingSockets.remove(socketID)) {
// Notify contacts of this users offline-going // Notify contacts of this users offline-going
envoy.server.data.User user = PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID)); final envoy.server.data.User user = PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID));
user.setStatus(UserStatus.OFFLINE);
user.setLastSeen(Instant.now()); user.setLastSeen(Instant.now());
UserStatusChangeProcessor.updateUserStatus(user); UserStatusChangeProcessor.updateUserStatus(user, UserStatus.OFFLINE);
// Remove the socket // Remove the socket
sockets.entrySet().removeIf(e -> e.getValue() == socketID); sockets.entrySet().removeIf(e -> e.getValue() == socketID);

View File

@ -46,42 +46,40 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
// Acquire a user object (or reject the handshake if that's impossible) // Acquire a user object (or reject the handshake if that's impossible)
User user = null; User user = null;
if (!credentials.isRegistration()) { if (!credentials.isRegistration()) try {
try { user = persistenceManager.getUserByName(credentials.getIdentifier());
user = persistenceManager.getUserByName(credentials.getIdentifier());
// Check if the user is already online // Check if the user is already online
if (connectionManager.isOnline(user.getID())) { if (connectionManager.isOnline(user.getID())) {
logger.warning(user + " is already online!"); logger.warning(user + " is already online!");
writeProxy.write(socketID, new HandshakeRejection(INTERNAL_ERROR)); writeProxy.write(socketID, new HandshakeRejection(INTERNAL_ERROR));
return;
}
// Authenticate with password or token
if (credentials.usesToken()) {
// Check the token
if (user.getAuthToken() == null || user.getAuthTokenExpiration().isBefore(Instant.now())
|| !user.getAuthToken().equals(credentials.getPassword())) {
logger.info(user + " tried to use an invalid token.");
writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN));
return;
}
} else {
// Check the password hash
if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
logger.info(user + " has entered the wrong password.");
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
return;
}
}
} catch (NoResultException e) {
logger.info("The requested user does not exist.");
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
return; return;
} }
} else {
// Authenticate with password or token
if (credentials.usesToken()) {
// Check the token
if (user.getAuthToken() == null || user.getAuthTokenExpiration().isBefore(Instant.now())
|| !user.getAuthToken().equals(credentials.getPassword())) {
logger.info(user + " tried to use an invalid token.");
writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN));
return;
}
} else
// Check the password hash
if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
logger.info(user + " has entered the wrong password.");
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
return;
}
} catch (final NoResultException e) {
logger.info("The requested user does not exist.");
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
return;
}
else {
// Validate user name // Validate user name
if (!Bounds.isValidContactName(credentials.getIdentifier())) { if (!Bounds.isValidContactName(credentials.getIdentifier())) {
@ -98,7 +96,7 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
logger.info("The requested user already exists."); logger.info("The requested user already exists.");
writeProxy.write(socketID, new HandshakeRejection(USERNAME_TAKEN)); writeProxy.write(socketID, new HandshakeRejection(USERNAME_TAKEN));
return; return;
} catch (NoResultException e) { } catch (final NoResultException e) {
// Creation of a new user // Creation of a new user
user = new User(); user = new User();
user.setName(credentials.getIdentifier()); user.setName(credentials.getIdentifier());
@ -115,18 +113,17 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
connectionManager.registerUser(user.getID(), socketID); connectionManager.registerUser(user.getID(), socketID);
// Change status and notify contacts about it // Change status and notify contacts about it
user.setStatus(ONLINE); UserStatusChangeProcessor.updateUserStatus(user, ONLINE);
UserStatusChangeProcessor.updateUserStatus(user);
// Process token request // Process token request
if (credentials.requestToken()) { if (credentials.requestToken()) {
String token; String token;
if (user.getAuthToken() != null && user.getAuthTokenExpiration().isAfter(Instant.now())) { if (user.getAuthToken() != null && user.getAuthTokenExpiration().isAfter(Instant.now()))
// Reuse existing token and delay expiration date // Reuse existing token and delay expiration date
token = user.getAuthToken(); token = user.getAuthToken();
} else { else {
// Generate new token // Generate new token
token = AuthTokenGenerator.nextToken(); token = AuthTokenGenerator.nextToken();
@ -141,13 +138,13 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
pendingMessages.removeIf(GroupMessage.class::isInstance); pendingMessages.removeIf(GroupMessage.class::isInstance);
logger.fine("Sending " + pendingMessages.size() + " pending messages to " + user + "..."); logger.fine("Sending " + pendingMessages.size() + " pending messages to " + user + "...");
for (var msg : pendingMessages) { for (final var msg : pendingMessages) {
final var msgCommon = msg.toCommon(); final var msgCommon = msg.toCommon();
if (msg.getCreationDate().isAfter(credentials.getLastSync())) { if (msg.getCreationDate().isAfter(credentials.getLastSync()))
// Sync without side effects // Sync without side effects
writeProxy.write(socketID, msgCommon); writeProxy.write(socketID, msgCommon);
} else if (msg.getStatus() == SENT) { else if (msg.getStatus() == SENT) {
// Send the message // Send the message
writeProxy.write(socketID, msgCommon); writeProxy.write(socketID, msgCommon);
@ -162,10 +159,10 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
} else writeProxy.write(socketID, new MessageStatusChange(msgCommon)); } else writeProxy.write(socketID, new MessageStatusChange(msgCommon));
} }
List<GroupMessage> pendingGroupMessages = PersistenceManager.getInstance().getPendingGroupMessages(user, credentials.getLastSync()); final List<GroupMessage> pendingGroupMessages = PersistenceManager.getInstance().getPendingGroupMessages(user, credentials.getLastSync());
logger.fine("Sending " + pendingGroupMessages.size() + " pending group messages to " + user + "..."); logger.fine("Sending " + pendingGroupMessages.size() + " pending group messages to " + user + "...");
for (var gmsg : pendingGroupMessages) { for (final var gmsg : pendingGroupMessages) {
final var gmsgCommon = gmsg.toCommon(); final var gmsgCommon = gmsg.toCommon();
// Deliver the message to the user if he hasn't received it yet // Deliver the message to the user if he hasn't received it yet
@ -189,20 +186,18 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
} }
PersistenceManager.getInstance().updateMessage(gmsg); PersistenceManager.getInstance().updateMessage(gmsg);
} else { } else
// Just send the message without updating if it was received in the past // Just send the message without updating if it was received in the past
writeProxy.write(socketID, gmsgCommon); writeProxy.write(socketID, gmsgCommon);
}
} else { } else {
// Sending group message status changes // Sending group message status changes
if (gmsg.getStatus() == SENT && gmsg.getLastStatusChangeDate().isAfter(gmsg.getCreationDate()) if (gmsg.getStatus() == SENT && gmsg.getLastStatusChangeDate().isAfter(gmsg.getCreationDate())
|| gmsg.getStatus() == RECEIVED && gmsg.getLastStatusChangeDate().isAfter(gmsg.getReceivedDate())) { || gmsg.getStatus() == RECEIVED && gmsg.getLastStatusChangeDate().isAfter(gmsg.getReceivedDate()))
gmsg.getMemberMessageStatus() gmsg.getMemberMessageStatus()
.forEach((memberID, memberStatus) -> writeProxy.write(socketID, .forEach((memberID, memberStatus) -> writeProxy.write(socketID,
new GroupMessageStatusChange(gmsg.getID(), memberStatus, gmsg.getLastStatusChangeDate(), memberID))); new GroupMessageStatusChange(gmsg.getID(), memberStatus, gmsg.getLastStatusChangeDate(), memberID)));
}
// Deliver just a status change instead of the whole message // Deliver just a status change instead of the whole message
if (gmsg.getStatus() == RECEIVED && user.getLastSeen().isBefore(gmsg.getReceivedDate()) if (gmsg.getStatus() == RECEIVED && user.getLastSeen().isBefore(gmsg.getReceivedDate())

View File

@ -28,18 +28,20 @@ public final class UserStatusChangeProcessor implements ObjectProcessor<UserStat
logger.warning("Received an unnecessary UserStatusChange"); logger.warning("Received an unnecessary UserStatusChange");
return; return;
} }
updateUserStatus(input); updateUserStatus(persistenceManager.getUserByID(input.getID()), input.get());
} }
/** /**
* Sets the {@link UserStatus} for a given user. Both offline contacts and * Sets the {@link UserStatus} for a given user. Both offline contacts and
* currently online contacts are notified. * currently online contacts are notified.
* *
* @param user the {@link UserStatusChange} that signals the change * @param user the user whose status has changed
* @param newStatus the new status of that user
* @since Envoy Server Standalone v0.1-alpha * @since Envoy Server Standalone v0.1-alpha
*/ */
public static void updateUserStatus(User user) { public static void updateUserStatus(User user, UserStatus newStatus) {
user.setStatus(newStatus);
// Handling for newly logged in clients // Handling for newly logged in clients
persistenceManager.updateContact(user); persistenceManager.updateContact(user);
@ -48,12 +50,6 @@ public final class UserStatusChangeProcessor implements ObjectProcessor<UserStat
writeProxy.writeToOnlineContacts(user.getContacts(), new UserStatusChange(user.getID(), user.getStatus())); writeProxy.writeToOnlineContacts(user.getContacts(), new UserStatusChange(user.getID(), user.getStatus()));
} }
/**
* @param evt the {@link UserStatusChange}
* @since Envoy Server Standalone v0.1-alpha
*/
public static void updateUserStatus(UserStatusChange evt) { updateUserStatus(persistenceManager.getUserByID(evt.getID())); }
/** /**
* This method is only called by the LoginCredentialProcessor because every * This method is only called by the LoginCredentialProcessor because every
* user needs to login (open a socket) before changing his status. * user needs to login (open a socket) before changing his status.