Compare commits

..

2 Commits

Author SHA1 Message Date
Kai S. K. Engelbart 6b02fd0f46
Add Datamodel 2020-10-24 12:39:51 +02:00
Maximilian P. Käfer a062911d55 Handshake Sequence UML 2020-10-24 12:37:33 +02:00
67 changed files with 1200 additions and 711 deletions

37
Jenkinsfile vendored
View File

@ -1,37 +0,0 @@
pipeline {
agent any
options {
ansiColor('xterm')
}
stages {
stage('Build') {
steps {
sh 'mvn -DskipTests clean package'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
post {
always {
junit '*/target/surefire-reports/*.xml'
}
}
}
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('KSKE SonarQube') {
sh 'mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.9.1.2184:sonar'
}
}
}
}
post {
success {
archiveArtifacts artifacts: 'client/target/envoy-client-*-shaded.jar, server/target/envoy-server-jar-with-dependencies.jar'
}
}
}

View File

@ -17,8 +17,6 @@ If you want to transfer a file to another user, you can attach it to a message.
On the settings page some convenience features can be configured, as well as the color theme. On the settings page some convenience features can be configured, as well as the color theme.
Additional info on how to use Envoy can be found [here](https://git.kske.dev/zdm/envoy/wiki) in the client section.
### System requirements ### System requirements
To run Envoy, you have to install a Java Runtime Environment (JRE) of at least version 11. To run Envoy, you have to install a Java Runtime Environment (JRE) of at least version 11.
@ -31,7 +29,7 @@ Most major Linux distributions like Debian, Arch and Gentoo have a Noto emoji pa
To set up an Envoy server, download the package from the release page. To set up an Envoy server, download the package from the release page.
To configure the behavior of Envoy Server, please have a look at the [documentation](https://git.kske.dev/zdm/envoy/wiki), specifically the server part. Because the project lacks external documentation for the moment, please refer to the Javadoc inside the source code to configure your Envoy instance.
### System requirements ### System requirements

View File

@ -21,6 +21,7 @@
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes> <attributes>
<attribute name="maven.pomderived" value="true"/> <attribute name="maven.pomderived" value="true"/>
<attribute name="module" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"> <classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">

View File

@ -22,21 +22,20 @@ import envoy.client.net.WriteProxy;
*/ */
public class Chat implements Serializable { public class Chat implements Serializable {
protected transient ObservableList<Message> messages = FXCollections.observableArrayList(); protected boolean disabled;
protected final Contact recipient;
protected boolean disabled;
protected boolean underlyingContactDeleted;
/** /**
* Stores the last time an {@link envoy.event.IsTyping} event has been sent. * Stores the last time an {@link envoy.event.IsTyping} event has been sent.
*/ */
protected transient long lastWritingEvent; protected transient long lastWritingEvent;
protected transient ObservableList<Message> messages = FXCollections.observableArrayList();
protected int unreadAmount; protected int unreadAmount;
protected static IntegerProperty totalUnreadAmount = new SimpleIntegerProperty(); protected static IntegerProperty totalUnreadAmount = new SimpleIntegerProperty();
protected final Contact recipient;
private static final long serialVersionUID = 2L; private static final long serialVersionUID = 2L;
/** /**

View File

@ -13,8 +13,9 @@ import java.util.stream.Stream;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.*; import javafx.collections.*;
import dev.kske.eventbus.core.*; import dev.kske.eventbus.Event;
import dev.kske.eventbus.core.Event; import dev.kske.eventbus.EventBus;
import dev.kske.eventbus.EventListener;
import envoy.data.*; import envoy.data.*;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
@ -34,7 +35,7 @@ import envoy.client.event.*;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public final class LocalDB { public final class LocalDB implements EventListener {
// Data // Data
private User user; private User user;
@ -245,13 +246,8 @@ public final class LocalDB {
* @throws IOException if the saving process failed * @throws IOException if the saving process failed
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
@Event(EnvoyCloseEvent.class) @Event(eventType = EnvoyCloseEvent.class, priority = 500)
@Priority(500)
private synchronized void save() { 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..."); EnvoyLog.getLogger(LocalDB.class).log(Level.FINER, "Saving local database...");
// Save users // Save users
@ -277,66 +273,37 @@ public final class LocalDB {
} }
} }
/** @Event(priority = 500)
* 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) { private void onMessage(Message msg) {
if (msg.getStatus() == MessageStatus.SENT) if (msg.getStatus() == MessageStatus.SENT)
msg.nextStatus(); msg.nextStatus();
} }
@Event @Event(priority = 500)
@Priority(500)
private void onGroupMessage(GroupMessage msg) { private void onGroupMessage(GroupMessage msg) {
// TODO: Cancel event once EventBus is updated // TODO: Cancel event once EventBus is updated
if (msg.getStatus() == MessageStatus.WAITING || msg.getStatus() == MessageStatus.READ) if (msg.getStatus() == MessageStatus.WAITING || msg.getStatus() == MessageStatus.READ)
logger.warning("The groupMessage has the unexpected status " + msg.getStatus()); logger.warning("The groupMessage has the unexpected status " + msg.getStatus());
} }
@Event @Event(priority = 500)
@Priority(500)
private void onMessageStatusChange(MessageStatusChange evt) { private void onMessageStatusChange(MessageStatusChange evt) {
getMessage(evt.getID()).ifPresent(msg -> msg.setStatus(evt.get())); getMessage(evt.getID()).ifPresent(msg -> msg.setStatus(evt.get()));
} }
@Event @Event(priority = 500)
@Priority(500)
private void onGroupMessageStatusChange(GroupMessageStatusChange evt) { private void onGroupMessageStatusChange(GroupMessageStatusChange evt) {
this.<GroupMessage>getMessage(evt.getID()) this.<GroupMessage>getMessage(evt.getID())
.ifPresent(msg -> msg.getMemberStatuses().replace(evt.getMemberID(), evt.get())); .ifPresent(msg -> msg.getMemberStatuses().replace(evt.getMemberID(), evt.get()));
} }
@Event @Event(priority = 500)
@Priority(500)
private void onUserStatusChange(UserStatusChange evt) { private void onUserStatusChange(UserStatusChange evt) {
getChat(evt.getID()).map(Chat::getRecipient).map(User.class::cast) getChat(evt.getID()).map(Chat::getRecipient).map(User.class::cast)
.ifPresent(u -> u.setStatus(evt.get())); .ifPresent(u -> u.setStatus(evt.get()));
} }
@Event @Event(priority = 500)
@Priority(500)
private void onUserOperation(UserOperation operation) { private void onUserOperation(UserOperation operation) {
final var eventUser = operation.get(); final var eventUser = operation.get();
switch (operation.getOperationType()) { switch (operation.getOperationType()) {
@ -362,15 +329,13 @@ public final class LocalDB {
Platform.runLater(() -> chats.add(new GroupChat(user, newGroup))); Platform.runLater(() -> chats.add(new GroupChat(user, newGroup)));
} }
@Event @Event(priority = 500)
@Priority(500)
private void onGroupResize(GroupResize evt) { private void onGroupResize(GroupResize evt) {
getChat(evt.getGroupID()).map(Chat::getRecipient).map(Group.class::cast) getChat(evt.getGroupID()).map(Chat::getRecipient).map(Group.class::cast)
.ifPresent(evt::apply); .ifPresent(evt::apply);
} }
@Event @Event(priority = 500)
@Priority(500)
private void onNameChange(NameChange evt) { private void onNameChange(NameChange evt) {
chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == evt.getID()).findAny() chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == evt.getID()).findAny()
.ifPresent(c -> c.setName(evt.get())); .ifPresent(c -> c.setName(evt.get()));
@ -392,8 +357,7 @@ public final class LocalDB {
* *
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
@Event(Logout.class) @Event(eventType = Logout.class, priority = 50)
@Priority(50)
private void onLogout() { private void onLogout() {
autoSaver.cancel(); autoSaver.cancel();
autoSaveRestart = true; autoSaveRestart = true;
@ -425,33 +389,21 @@ public final class LocalDB {
}); });
} }
@Event @Event(priority = 500)
@Priority(500)
private void onOwnStatusChange(OwnStatusChange statusChange) { private void onOwnStatusChange(OwnStatusChange statusChange) {
user.setStatus(statusChange.get()); user.setStatus(statusChange.get());
} }
@Event(ContactsChangedSinceLastLogin.class) @Event(eventType = ContactsChangedSinceLastLogin.class, priority = 500)
@Priority(500)
private void onContactsChangedSinceLastLogin() { private void onContactsChangedSinceLastLogin() {
contactsChanged = true; contactsChanged = true;
} }
@Event @Event(priority = 500)
@Priority(500)
private void onContactDisabled(ContactDisabled event) { private void onContactDisabled(ContactDisabled event) {
getChat(event.get().getID()).ifPresent(chat -> chat.setDisabled(true)); 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 * @return a {@code Map<String, User>} of all users stored locally with their user names as keys
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha
@ -509,8 +461,7 @@ public final class LocalDB {
* @param idGenerator the message ID generator to set * @param idGenerator the message ID generator to set
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
@Event @Event(priority = 150)
@Priority(150)
public void setIDGenerator(IDGenerator idGenerator) { this.idGenerator = idGenerator; } public void setIDGenerator(IDGenerator idGenerator) { this.idGenerator = idGenerator; }
/** /**

View File

@ -5,7 +5,8 @@ import java.util.*;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import dev.kske.eventbus.core.*; import dev.kske.eventbus.*;
import dev.kske.eventbus.EventListener;
import envoy.util.*; import envoy.util.*;
@ -20,7 +21,7 @@ import envoy.client.event.EnvoyCloseEvent;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public final class Settings { public final class Settings implements EventListener {
// Actual settings accessible by the rest of the application // Actual settings accessible by the rest of the application
private Map<String, SettingsItem<?>> items; private Map<String, SettingsItem<?>> items;
@ -68,7 +69,7 @@ public final class Settings {
* @throws IOException if an error occurs while saving the themes * @throws IOException if an error occurs while saving the themes
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
@Event(EnvoyCloseEvent.class) @Event(eventType = EnvoyCloseEvent.class)
private void save() { private void save() {
EnvoyLog.getLogger(Settings.class).log(Level.INFO, "Saving settings..."); EnvoyLog.getLogger(Settings.class).log(Level.INFO, "Saving settings...");

View File

@ -6,7 +6,7 @@ import envoy.data.User.UserStatus;
import envoy.client.data.Context; import envoy.client.data.Context;
import envoy.client.helper.ShutdownHelper; import envoy.client.helper.ShutdownHelper;
import envoy.client.ui.SceneInfo; import envoy.client.ui.SceneContext.SceneInfo;
import envoy.client.util.UserUtil; import envoy.client.util.UserUtil;
/** /**

View File

@ -4,7 +4,7 @@ import java.util.*;
import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyCombination;
import envoy.client.ui.SceneInfo; import envoy.client.ui.SceneContext.SceneInfo;
/** /**
* Contains all keyboard shortcuts used throughout the application. * Contains all keyboard shortcuts used throughout the application.

View File

@ -1,22 +0,0 @@
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);
}
}

View File

@ -1,6 +1,6 @@
package envoy.client.helper; package envoy.client.helper;
import dev.kske.eventbus.core.EventBus; import dev.kske.eventbus.EventBus;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.event.EnvoyCloseEvent; import envoy.client.event.EnvoyCloseEvent;

View File

@ -5,14 +5,14 @@ import java.net.Socket;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.logging.*; import java.util.logging.*;
import dev.kske.eventbus.core.*; import dev.kske.eventbus.*;
import dev.kske.eventbus.core.Event; import dev.kske.eventbus.Event;
import envoy.data.*; import envoy.data.*;
import envoy.event.*; import envoy.event.*;
import envoy.util.*; import envoy.util.*;
import envoy.client.data.ClientConfig; import envoy.client.data.*;
import envoy.client.event.EnvoyCloseEvent; import envoy.client.event.EnvoyCloseEvent;
/** /**
@ -24,7 +24,7 @@ import envoy.client.event.EnvoyCloseEvent;
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public final class Client implements Closeable { public final class Client implements EventListener, Closeable {
// Connection handling // Connection handling
private Socket socket; private Socket socket;
@ -55,12 +55,13 @@ public final class Client implements Closeable {
* the handshake does exceed this time limit, an exception is thrown. * the handshake does exceed this time limit, an exception is thrown.
* *
* @param credentials the login credentials of the user * @param credentials the login credentials of the user
* @param cacheMap the map of all caches needed
* @throws TimeoutException if the server could not be reached * @throws TimeoutException if the server could not be reached
* @throws IOException if the login credentials could not be written * @throws IOException if the login credentials could not be written
* @throws InterruptedException if the current thread is interrupted while waiting for the * @throws InterruptedException if the current thread is interrupted while waiting for the
* handshake response * handshake response
*/ */
public void performHandshake(LoginCredentials credentials) public void performHandshake(LoginCredentials credentials, CacheMap cacheMap)
throws TimeoutException, IOException, InterruptedException { throws TimeoutException, IOException, InterruptedException {
if (online) if (online)
throw new IllegalStateException("Handshake has already been performed successfully"); throw new IllegalStateException("Handshake has already been performed successfully");
@ -78,6 +79,7 @@ public final class Client implements Closeable {
// Register user creation processor, contact list processor, message cache and // Register user creation processor, contact list processor, message cache and
// authentication token // authentication token
receiver.registerProcessor(User.class, sender -> this.sender = sender); receiver.registerProcessor(User.class, sender -> this.sender = sender);
receiver.registerProcessors(cacheMap.getMap());
// Start receiver // Start receiver
receiver.start(); receiver.start();
@ -99,20 +101,44 @@ public final class Client implements Closeable {
if (System.currentTimeMillis() - start > 5000) { if (System.currentTimeMillis() - start > 5000) {
rejected = true; rejected = true;
socket.close();
receiver.removeAllProcessors();
throw new TimeoutException("Did not log in after 5 seconds"); throw new TimeoutException("Did not log in after 5 seconds");
} }
Thread.sleep(500); Thread.sleep(500);
} }
// Remove handshake specific processors
receiver.removeAllProcessors();
online = true; online = true;
logger.log(Level.INFO, "Handshake completed."); logger.log(Level.INFO, "Handshake completed.");
} }
/**
* Initializes the {@link Receiver} used to process data sent from the server to this client.
*
* @param localDB the local database used to persist the current {@link IDGenerator}
* @param cacheMap the map of all caches needed
* @throws IOException if no {@link IDGenerator} is present and none could be requested from the
* server
* @since Envoy Client v0.2-alpha
*/
public void initReceiver(LocalDB localDB, CacheMap cacheMap) throws IOException {
checkOnline();
// Remove all processors as they are only used during the handshake
receiver.removeAllProcessors();
// Relay cached messages and message status changes
cacheMap.get(Message.class).setProcessor(eventBus::dispatch);
cacheMap.get(GroupMessage.class).setProcessor(eventBus::dispatch);
cacheMap.get(MessageStatusChange.class).setProcessor(eventBus::dispatch);
cacheMap.get(GroupMessageStatusChange.class).setProcessor(eventBus::dispatch);
// Request a generator if none is present or the existing one is consumed
if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext())
requestIDGenerator();
// Relay caches
cacheMap.getMap().values().forEach(Cache::relay);
}
/** /**
* Sends an object to the server. * Sends an object to the server.
* *
@ -153,15 +179,13 @@ public final class Client implements Closeable {
send(new IDGeneratorRequest()); send(new IDGeneratorRequest());
} }
@Event(HandshakeRejection.class) @Event(eventType = HandshakeRejection.class, priority = 1000)
@Priority(1000)
private void onHandshakeRejection() { private void onHandshakeRejection() {
rejected = true; rejected = true;
} }
@Override @Override
@Event(EnvoyCloseEvent.class) @Event(eventType = EnvoyCloseEvent.class, priority = 50)
@Priority(50)
public void close() { public void close() {
if (online) { if (online) {
logger.log(Level.INFO, "Closing connection..."); logger.log(Level.INFO, "Closing connection...");

View File

@ -6,7 +6,7 @@ import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.*; import java.util.logging.*;
import dev.kske.eventbus.core.EventBus; import dev.kske.eventbus.*;
import envoy.util.*; import envoy.util.*;
@ -87,17 +87,15 @@ public final class Receiver extends Thread {
// Dispatch to the processor if present // Dispatch to the processor if present
if (processor != null) if (processor != null)
processor.accept(obj); processor.accept(obj);
// Dispatch to the event bus if the object has no processor // Dispatch to the event bus if the object is an event without a processor
else else if (obj instanceof IEvent)
eventBus.dispatch(obj); eventBus.dispatch((IEvent) obj);
// TODO: Log DeadEvent from Event Bus 1.1.0
// Notify if no processor could be located // Notify if no processor could be located
// else else
// logger.log(Level.WARNING, logger.log(Level.WARNING,
// String.format( String.format(
// "The received object has the %s for which no processor is defined.", "The received object has the %s for which no processor is defined.",
// obj.getClass())); obj.getClass()));
} }
} catch (final SocketException | EOFException e) { } catch (final SocketException | EOFException e) {
// Connection probably closed by client. // Connection probably closed by client.

View File

@ -4,11 +4,12 @@ import java.io.IOException;
import java.util.Stack; import java.util.Stack;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.*; import javafx.scene.*;
import javafx.stage.Stage; import javafx.stage.Stage;
import dev.kske.eventbus.core.*; import dev.kske.eventbus.*;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
@ -25,13 +26,53 @@ import envoy.client.event.*;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public final class SceneContext { public final class SceneContext implements EventListener {
/**
* Contains information about different scenes and their FXML resource files.
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public enum SceneInfo {
/**
* The main scene in which the chat screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
CHAT_SCENE("/fxml/ChatScene.fxml"),
/**
* The scene in which the settings screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
SETTINGS_SCENE("/fxml/SettingsScene.fxml"),
/**
* The scene in which the login screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
LOGIN_SCENE("/fxml/LoginScene.fxml");
/**
* The path to the FXML resource.
*/
public final String path;
SceneInfo(String path) {
this.path = path;
}
}
private final Stage stage; private final Stage stage;
private final Stack<Parent> roots = new Stack<>(); private final FXMLLoader loader = new FXMLLoader();
private final Stack<Object> controllers = new Stack<>(); private final Stack<Scene> sceneStack = new Stack<>();
private final Stack<Object> controllerStack = new Stack<>();
private Scene scene; private static final Settings settings = Settings.getInstance();
/** /**
* Initializes the scene context. * Initializes the scene context.
@ -47,44 +88,44 @@ public final class SceneContext {
/** /**
* Loads a new scene specified by a scene info. * Loads a new scene specified by a scene info.
* *
* @param info specifies the scene to load * @param sceneInfo specifies the scene to load
* @throws RuntimeException if the loading process fails * @throws RuntimeException if the loading process fails
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public void load(SceneInfo info) { public void load(SceneInfo sceneInfo) {
EnvoyLog.getLogger(SceneContext.class).log(Level.FINER, "Loading scene " + info); EnvoyLog.getLogger(SceneContext.class).log(Level.FINER, "Loading scene " + sceneInfo);
loader.setRoot(null);
loader.setController(null);
try { try {
final var rootNode =
(Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
final var scene = new Scene(rootNode);
final var controller = loader.getController();
controllerStack.push(controller);
// Load root node and controller sceneStack.push(scene);
var loader = new FXMLLoader(); stage.setScene(scene);
Parent root = loader.load(getClass().getResourceAsStream(info.path));
Object controller = loader.getController();
roots.push(root);
controllers.push(controller);
if (scene == null) {
// One-time scene initialization
scene = new Scene(root, stage.getWidth(), stage.getHeight());
applyCSS();
stage.setScene(scene);
} else {
scene.setRoot(root);
}
// Remove previous keyboard shortcuts
scene.getAccelerators().clear();
// Supply the global custom keyboard shortcuts for that scene // Supply the global custom keyboard shortcuts for that scene
scene.getAccelerators() scene.getAccelerators()
.putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(info)); .putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(sceneInfo));
// Supply the scene specific keyboard shortcuts // Supply the scene specific keyboard shortcuts
if (controller instanceof KeyboardMapping) if (controller instanceof KeyboardMapping)
scene.getAccelerators() scene.getAccelerators()
.putAll(((KeyboardMapping) controller).getKeyboardShortcuts()); .putAll(((KeyboardMapping) controller).getKeyboardShortcuts());
} catch (IOException e) {
// The LoginScene is the only scene not intended to be resized
// As strange as it seems, this is needed as otherwise the LoginScene won't be
// displayed on some OS (...Debian...)
stage.sizeToScene();
Platform.runLater(() -> stage.setResizable(sceneInfo != SceneInfo.LOGIN_SCENE));
applyCSS();
stage.show();
} catch (final IOException e) {
EnvoyLog.getLogger(SceneContext.class).log(Level.SEVERE,
String.format("Could not load scene for %s: ", sceneInfo), e);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@ -96,45 +137,42 @@ public final class SceneContext {
*/ */
public void pop() { public void pop() {
// Pop current root node and controller // Pop scene and controller
roots.pop(); sceneStack.pop();
controllers.pop(); controllerStack.pop();
// Apply new scene if present // Apply new scene if present
if (!roots.isEmpty()) { if (!sceneStack.isEmpty()) {
scene.setRoot(roots.peek()); final var newScene = sceneStack.peek();
stage.setScene(newScene);
// Invoke restore if controller is restorable applyCSS();
var controller = controllers.peek(); stage.sizeToScene();
// If the controller implements the Restorable interface,
// the actions to perform on restoration will be executed here
final var controller = controllerStack.peek();
if (controller instanceof Restorable) if (controller instanceof Restorable)
((Restorable) controller).onRestore(); ((Restorable) controller).onRestore();
} else {
// Remove the current scene entirely
scene = null;
stage.setScene(null);
} }
stage.show();
} }
private void applyCSS() { private void applyCSS() {
if (scene != null) { if (!sceneStack.isEmpty()) {
var styleSheets = scene.getStylesheets(); final var styleSheets = stage.getScene().getStylesheets();
var themeCSS = "/css/" + Settings.getInstance().getCurrentTheme() + ".css"; final var themeCSS = "/css/" + settings.getCurrentTheme() + ".css";
styleSheets.clear(); styleSheets.clear();
styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(), styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(),
getClass().getResource(themeCSS).toExternalForm()); getClass().getResource(themeCSS).toExternalForm());
} }
} }
@Event(Logout.class) @Event(eventType = Logout.class, priority = 150)
@Priority(150)
private void onLogout() { private void onLogout() {
roots.clear(); sceneStack.clear();
controllers.clear(); controllerStack.clear();
} }
@Event(ThemeChangeEvent.class) @Event(priority = 150, eventType = ThemeChangeEvent.class)
@Priority(150)
private void onThemeChange() { private void onThemeChange() {
applyCSS(); applyCSS();
} }
@ -144,7 +182,7 @@ public final class SceneContext {
* @return the controller used by the current scene * @return the controller used by the current scene
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public <T> T getController() { return (T) controllers.peek(); } public <T> T getController() { return (T) controllerStack.peek(); }
/** /**
* @return the stage in which the scenes are displayed * @return the stage in which the scenes are displayed
@ -156,5 +194,5 @@ public final class SceneContext {
* @return whether the scene stack is empty * @return whether the scene stack is empty
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public boolean isEmpty() { return roots.isEmpty(); } public boolean isEmpty() { return sceneStack.isEmpty(); }
} }

View File

@ -1,40 +0,0 @@
package envoy.client.ui;
/**
* Contains information about different scenes and their FXML resource files.
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public enum SceneInfo {
/**
* The main scene in which the chat screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
CHAT_SCENE("/fxml/ChatScene.fxml"),
/**
* The scene in which the settings screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
SETTINGS_SCENE("/fxml/SettingsScene.fxml"),
/**
* The scene in which the login screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
LOGIN_SCENE("/fxml/LoginScene.fxml");
/**
* The path to the FXML resource.
*/
public final String path;
SceneInfo(String path) {
this.path = path;
}
}

View File

@ -12,7 +12,7 @@ import javafx.stage.Stage;
import envoy.data.*; import envoy.data.*;
import envoy.data.User.UserStatus; import envoy.data.User.UserStatus;
import envoy.event.UserStatusChange; import envoy.event.*;
import envoy.exception.EnvoyException; import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
@ -20,6 +20,7 @@ import envoy.client.data.*;
import envoy.client.data.shortcuts.EnvoyShortcutConfig; import envoy.client.data.shortcuts.EnvoyShortcutConfig;
import envoy.client.helper.ShutdownHelper; import envoy.client.helper.ShutdownHelper;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.ui.SceneContext.SceneInfo;
import envoy.client.ui.controller.LoginScene; import envoy.client.ui.controller.LoginScene;
import envoy.client.util.IconUtil; import envoy.client.util.IconUtil;
@ -93,7 +94,7 @@ public final class Startup extends Application {
final var sceneContext = new SceneContext(stage); final var sceneContext = new SceneContext(stage);
context.setSceneContext(sceneContext); context.setSceneContext(sceneContext);
// Authenticate with token if present or load login scene // Authenticate with token if present
if (localDB.getAuthToken() != null) { if (localDB.getAuthToken() != null) {
logger.info("Attempting authentication with token..."); logger.info("Attempting authentication with token...");
localDB.loadUserData(); localDB.loadUserData();
@ -102,9 +103,8 @@ public final class Startup extends Application {
VERSION, localDB.getLastSync()))) VERSION, localDB.getLastSync())))
sceneContext.load(SceneInfo.LOGIN_SCENE); sceneContext.load(SceneInfo.LOGIN_SCENE);
} else } else
// Load login scene
sceneContext.load(SceneInfo.LOGIN_SCENE); sceneContext.load(SceneInfo.LOGIN_SCENE);
stage.show();
} }
/** /**
@ -115,20 +115,21 @@ public final class Startup extends Application {
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public static boolean performHandshake(LoginCredentials credentials) { public static boolean performHandshake(LoginCredentials credentials) {
final var cacheMap = new CacheMap();
cacheMap.put(Message.class, new Cache<Message>());
cacheMap.put(GroupMessage.class, new Cache<GroupMessage>());
cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>());
cacheMap.put(GroupMessageStatusChange.class, new Cache<GroupMessageStatusChange>());
final var originalStatus = final var originalStatus =
localDB.getUser() == null ? UserStatus.ONLINE : localDB.getUser().getStatus(); localDB.getUser() == null ? UserStatus.ONLINE : localDB.getUser().getStatus();
try { try {
client.performHandshake(credentials); client.performHandshake(credentials, cacheMap);
if (client.isOnline()) { if (client.isOnline()) {
// Restore the original status as the server automatically returns status ONLINE // Restore the original status as the server automatically returns status ONLINE
client.getSender().setStatus(originalStatus); client.getSender().setStatus(originalStatus);
loadChatScene(); loadChatScene();
client.initReceiver(localDB, cacheMap);
// Request an ID generator if none is present or the existing one is consumed
if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext())
client.requestIDGenerator();
return true; return true;
} else } else
return false; return false;
@ -225,7 +226,7 @@ public final class Startup extends Application {
// Load ChatScene // Load ChatScene
stage.setMinHeight(400); stage.setMinHeight(400);
stage.setMinWidth(843); stage.setMinWidth(843);
context.getSceneContext().load(SceneInfo.CHAT_SCENE); context.getSceneContext().load(SceneContext.SceneInfo.CHAT_SCENE);
stage.centerOnScreen(); stage.centerOnScreen();
// Exit or minimize the stage when a close request occurs // Exit or minimize the stage when a close request occurs

View File

@ -9,8 +9,8 @@ import java.awt.image.BufferedImage;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.stage.Stage; import javafx.stage.Stage;
import dev.kske.eventbus.core.Event; import dev.kske.eventbus.*;
import dev.kske.eventbus.core.EventBus; import dev.kske.eventbus.Event;
import envoy.data.Message; import envoy.data.Message;
import envoy.data.User.UserStatus; import envoy.data.User.UserStatus;
@ -31,7 +31,7 @@ import envoy.client.util.*;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public final class StatusTrayIcon { public final class StatusTrayIcon implements EventListener {
/** /**
* The {@link TrayIcon} provided by the System Tray API for controlling the system tray. This * The {@link TrayIcon} provided by the System Tray API for controlling the system tray. This
@ -136,7 +136,7 @@ public final class StatusTrayIcon {
* *
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
@Event(Logout.class) @Event(eventType = Logout.class)
public void hide() { public void hide() {
SystemTray.getSystemTray().remove(trayIcon); SystemTray.getSystemTray().remove(trayIcon);
} }

View File

@ -15,7 +15,7 @@ import envoy.util.EnvoyLog;
import envoy.client.data.Context; import envoy.client.data.Context;
import envoy.client.data.commands.*; import envoy.client.data.commands.*;
import envoy.client.helper.ShutdownHelper; import envoy.client.helper.ShutdownHelper;
import envoy.client.ui.SceneInfo; import envoy.client.ui.SceneContext.SceneInfo;
import envoy.client.ui.controller.ChatScene; import envoy.client.ui.controller.ChatScene;
import envoy.client.util.*; import envoy.client.util.*;
@ -32,7 +32,7 @@ public final class ChatSceneCommands {
private final SystemCommandBuilder builder = private final SystemCommandBuilder builder =
new SystemCommandBuilder(messageTextAreaCommands); new SystemCommandBuilder(messageTextAreaCommands);
private static final String messageDependentCommandDescription = private static final String messageDependantCommandDescription =
" the given message. Use s/S to use the selected message. Otherwise expects a number relative to the uppermost completely visible message."; " the given message. Use s/S to use the selected message. Otherwise expects a number relative to the uppermost completely visible message.";
/** /**
@ -141,7 +141,7 @@ public final class ChatSceneCommands {
else else
useRelativeMessage(command, action, additionalCheck, positionalArgument, false); useRelativeMessage(command, action, additionalCheck, positionalArgument, false);
}).setDefaults("s").setNumberOfArguments(1) }).setDefaults("s").setNumberOfArguments(1)
.setDescription(description.concat(messageDependentCommandDescription)).build(command); .setDescription(description.concat(messageDependantCommandDescription)).build(command);
} }
private void selectionNeighbor(Consumer<Message> action, Predicate<Message> additionalCheck, private void selectionNeighbor(Consumer<Message> action, Predicate<Message> additionalCheck,

View File

@ -18,7 +18,7 @@ import envoy.client.util.IconUtil;
*/ */
public final class ChatControl extends HBox { public final class ChatControl extends HBox {
private static Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32), private static final Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32),
groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32); groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
/** /**
@ -60,14 +60,4 @@ public final class ChatControl extends HBox {
} }
getStyleClass().add("list-element"); getStyleClass().add("list-element");
} }
/**
* Reloads the default icons.
*
* @since Envoy Client v0.3-beta
*/
public static void reloadDefaultChatIcons() {
userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32);
groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
}
} }

View File

@ -1,14 +1,11 @@
package envoy.client.ui.controller; package envoy.client.ui.controller;
import static envoy.client.ui.SceneInfo.SETTINGS_SCENE;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
import java.io.*; import java.io.*;
import java.nio.file.Files; import java.nio.file.Files;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.logging.*; import java.util.logging.*;
import javafx.animation.RotateTransition; import javafx.animation.RotateTransition;
@ -21,14 +18,14 @@ import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.*; import javafx.scene.image.*;
import javafx.scene.input.*; import javafx.scene.input.*;
import javafx.scene.layout.HBox; import javafx.scene.layout.*;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import javafx.util.Duration; import javafx.util.Duration;
import dev.kske.eventbus.core.*; import dev.kske.eventbus.*;
import dev.kske.eventbus.core.Event; import dev.kske.eventbus.Event;
import envoy.data.*; import envoy.data.*;
import envoy.data.Attachment.AttachmentType; import envoy.data.Attachment.AttachmentType;
@ -40,7 +37,6 @@ import envoy.util.EnvoyLog;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.data.audio.AudioRecorder; import envoy.client.data.audio.AudioRecorder;
import envoy.client.data.shortcuts.KeyboardMapping;
import envoy.client.event.*; import envoy.client.event.*;
import envoy.client.net.*; import envoy.client.net.*;
import envoy.client.ui.*; import envoy.client.ui.*;
@ -55,7 +51,7 @@ import envoy.client.util.*;
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public final class ChatScene implements Restorable, KeyboardMapping { public final class ChatScene implements EventListener, Restorable {
@FXML @FXML
private ListView<Message> messageList; private ListView<Message> messageList;
@ -220,13 +216,12 @@ public final class ChatScene implements Restorable, KeyboardMapping {
}); });
} }
@Event(BackEvent.class) @Event(eventType = BackEvent.class)
private void onBackEvent() { private void onBackEvent() {
tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal()); tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal());
} }
@Event @Event(includeSubtypes = true)
@Polymorphic
private void onMessage(Message message) { private void onMessage(Message message) {
// The sender of the message is the recipient of the chat // The sender of the message is the recipient of the chat
@ -305,7 +300,7 @@ public final class ChatScene implements Restorable, KeyboardMapping {
})); }));
} }
@Event(NoAttachments.class) @Event(eventType = NoAttachments.class)
private void onNoAttachments() { private void onNoAttachments() {
Platform.runLater(() -> { Platform.runLater(() -> {
attachmentButton.setDisable(true); attachmentButton.setDisable(true);
@ -318,15 +313,13 @@ public final class ChatScene implements Restorable, KeyboardMapping {
}); });
} }
@Event @Event(priority = 150)
@Priority(150)
private void onGroupCreationResult(GroupCreationResult result) { private void onGroupCreationResult(GroupCreationResult result) {
Platform.runLater(() -> newGroupButton.setDisable(result.get() == null)); Platform.runLater(() -> newGroupButton.setDisable(result.get() == null));
} }
@Event(ThemeChangeEvent.class) @Event(eventType = ThemeChangeEvent.class)
private void onThemeChange() { private void onThemeChange() {
ChatControl.reloadDefaultChatIcons();
settingsButton.setGraphic( settingsButton.setGraphic(
new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE))); new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
voiceButton.setGraphic( voiceButton.setGraphic(
@ -348,17 +341,11 @@ public final class ChatScene implements Restorable, KeyboardMapping {
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43)); recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
} }
@Event(Logout.class) @Event(eventType = Logout.class, priority = 200)
@Priority(200)
private void onLogout() { private void onLogout() {
eventBus.removeListener(this); eventBus.removeListener(this);
} }
@Event(AccountDeletion.class)
private void onAccountDeletion() {
Platform.runLater(chatList::refresh);
}
@Override @Override
public void onRestore() { public void onRestore() {
updateRemainingCharsLabel(); updateRemainingCharsLabel();
@ -451,7 +438,7 @@ public final class ChatScene implements Restorable, KeyboardMapping {
*/ */
@FXML @FXML
private void settingsButtonClicked() { private void settingsButtonClicked() {
sceneContext.load(SETTINGS_SCENE); sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE);
} }
/** /**
@ -599,7 +586,7 @@ public final class ChatScene implements Restorable, KeyboardMapping {
// IsTyping#millisecondsActive // IsTyping#millisecondsActive
if (client.isOnline() && currentChat.getLastWritingEvent() if (client.isOnline() && currentChat.getLastWritingEvent()
+ IsTyping.millisecondsActive <= System.currentTimeMillis()) { + IsTyping.millisecondsActive <= System.currentTimeMillis()) {
client.send(new IsTyping(currentChat.getRecipient().getID())); client.send(new IsTyping(getChatID(), currentChat.getRecipient().getID()));
currentChat.lastWritingEventWasNow(); currentChat.lastWritingEventWasNow();
} }
@ -613,6 +600,19 @@ public final class ChatScene implements Restorable, KeyboardMapping {
checkPostConditions(false); checkPostConditions(false);
} }
/**
* Returns the id that should be used to send things to the server: the id of 'our' {@link User}
* if the recipient of that object is another User, else the id of the {@link Group} 'our' user
* is sending to.
*
* @return an id that can be sent to the server
* @since Envoy Client v0.2-beta
*/
private long getChatID() {
return currentChat.getRecipient() instanceof User ? client.getSender().getID()
: currentChat.getRecipient().getID();
}
/** /**
* @param e the keys that have been pressed * @param e the keys that have been pressed
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
@ -786,8 +786,7 @@ public final class ChatScene implements Restorable, KeyboardMapping {
attachmentView.setVisible(visible); attachmentView.setVisible(visible);
} }
@Event(OwnStatusChange.class) @Event(eventType = OwnStatusChange.class, priority = 50)
@Priority(50)
private void generateOwnStatusControl() { private void generateOwnStatusControl() {
// Update the own user status if present // Update the own user status if present
@ -798,7 +797,7 @@ public final class ChatScene implements Restorable, KeyboardMapping {
// Else prepend it to the HBox children // Else prepend it to the HBox children
final var ownUserControl = new ContactControl(localDB.getUser()); final var ownUserControl = new ContactControl(localDB.getUser());
ownUserControl.setAlignment(Pos.CENTER_LEFT); ownUserControl.setAlignment(Pos.CENTER_LEFT);
HBox.setHgrow(ownUserControl, javafx.scene.layout.Priority.NEVER); HBox.setHgrow(ownUserControl, Priority.NEVER);
ownContactControl.getChildren().add(1, ownUserControl); ownContactControl.getChildren().add(1, ownUserControl);
} }
} }
@ -885,25 +884,4 @@ public final class ChatScene implements Restorable, KeyboardMapping {
: c -> c.getRecipient().getName().toLowerCase() : c -> c.getRecipient().getName().toLowerCase()
.contains(contactSearch.getText().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());
});
}
} }

View File

@ -7,7 +7,7 @@ import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import dev.kske.eventbus.core.*; import dev.kske.eventbus.*;
import envoy.data.User; import envoy.data.User;
import envoy.event.ElementOperation; import envoy.event.ElementOperation;
@ -34,7 +34,7 @@ import envoy.client.ui.listcell.ListCellFactory;
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public class ContactSearchTab { public class ContactSearchTab implements EventListener {
@FXML @FXML
private TextArea searchBar; private TextArea searchBar;

View File

@ -10,7 +10,7 @@ import javafx.scene.control.*;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import dev.kske.eventbus.core.*; import dev.kske.eventbus.*;
import envoy.data.*; import envoy.data.*;
import envoy.event.GroupCreation; import envoy.event.GroupCreation;
@ -18,7 +18,7 @@ import envoy.event.contact.UserOperation;
import envoy.util.Bounds; import envoy.util.Bounds;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.event.*; import envoy.client.event.BackEvent;
import envoy.client.ui.control.*; import envoy.client.ui.control.*;
import envoy.client.ui.listcell.ListCellFactory; import envoy.client.ui.listcell.ListCellFactory;
@ -33,7 +33,7 @@ import envoy.client.ui.listcell.ListCellFactory;
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public class GroupCreationTab { public class GroupCreationTab implements EventListener {
@FXML @FXML
private Button createButton; private Button createButton;
@ -252,10 +252,4 @@ public class GroupCreationTab {
} }
}); });
} }
@Event
private void onAccountDeletion(AccountDeletion deletion) {
final var deletedID = deletion.get();
Platform.runLater(() -> userList.getItems().removeIf(user -> (user.getID() == deletedID)));
}
} }

View File

@ -10,7 +10,7 @@ import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import dev.kske.eventbus.core.*; import dev.kske.eventbus.*;
import envoy.data.LoginCredentials; import envoy.data.LoginCredentials;
import envoy.event.HandshakeRejection; import envoy.event.HandshakeRejection;
@ -27,7 +27,7 @@ import envoy.client.util.IconUtil;
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public final class LoginScene { public final class LoginScene implements EventListener {
@FXML @FXML
private TextField userTextField; private TextField userTextField;

View File

@ -2,7 +2,7 @@ package envoy.client.ui.settings;
import javafx.scene.control.*; import javafx.scene.control.*;
import dev.kske.eventbus.core.EventBus; import dev.kske.eventbus.EventBus;
import envoy.data.User.UserStatus; import envoy.data.User.UserStatus;

View File

@ -13,15 +13,14 @@ import javafx.scene.image.*;
import javafx.scene.input.InputEvent; import javafx.scene.input.InputEvent;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import javafx.util.Duration;
import dev.kske.eventbus.core.EventBus; import dev.kske.eventbus.EventBus;
import envoy.event.*; import envoy.event.*;
import envoy.util.*; import envoy.util.*;
import envoy.client.ui.control.ProfilePicImageView; import envoy.client.ui.control.ProfilePicImageView;
import envoy.client.util.*; import envoy.client.util.IconUtil;
/** /**
* @author Leon Hofmeister * @author Leon Hofmeister
@ -39,7 +38,6 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
private final PasswordField newPasswordField = new PasswordField(); private final PasswordField newPasswordField = new PasswordField();
private final PasswordField repeatNewPasswordField = new PasswordField(); private final PasswordField repeatNewPasswordField = new PasswordField();
private final Button saveButton = new Button("Save"); 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 EventBus eventBus = EventBus.getInstance();
private static final Logger logger = EnvoyLog.getLogger(UserSettingsPane.class); private static final Logger logger = EnvoyLog.getLogger(UserSettingsPane.class);
@ -114,19 +112,16 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
final PasswordField[] passwordFields = final PasswordField[] passwordFields =
{ currentPasswordField, newPasswordField, repeatNewPasswordField }; { currentPasswordField, newPasswordField, repeatNewPasswordField };
final EventHandler<? super InputEvent> passwordEntered = final EventHandler<? super InputEvent> passwordEntered = e -> {
e -> { newPassword =
newPassword = newPasswordField.getText();
newPasswordField validPassword = newPassword
.getText(); .equals(
validPassword = repeatNewPasswordField
newPassword.equals( .getText())
repeatNewPasswordField && !newPasswordField
.getText()) .getText().isBlank();
&& !newPasswordField };
.getText()
.isBlank();
};
newPasswordField.setOnInputMethodTextChanged(passwordEntered); newPasswordField.setOnInputMethodTextChanged(passwordEntered);
newPasswordField.setOnKeyTyped(passwordEntered); newPasswordField.setOnKeyTyped(passwordEntered);
repeatNewPasswordField.setOnInputMethodTextChanged(passwordEntered); repeatNewPasswordField.setOnInputMethodTextChanged(passwordEntered);
@ -142,21 +137,9 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
// Displaying the save button // Displaying the save button
saveButton saveButton
.setOnAction(e -> save(currentPasswordField.getText())); .setOnAction(e -> save(client.getSender().getID(), currentPasswordField.getText()));
saveButton.setAlignment(Pos.BOTTOM_RIGHT); saveButton.setAlignment(Pos.BOTTOM_RIGHT);
getChildren().add(saveButton); 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);
} }
/** /**
@ -165,11 +148,11 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
* @param username the new username * @param username the new username
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
private void save(String oldPassword) { private void save(long userID, String oldPassword) {
// The profile pic was changed // The profile pic was changed
if (profilePicChanged) { if (profilePicChanged) {
final var profilePicChangeEvent = new ProfilePicChange(currentImageBytes); final var profilePicChangeEvent = new ProfilePicChange(currentImageBytes, userID);
eventBus.dispatch(profilePicChangeEvent); eventBus.dispatch(profilePicChangeEvent);
client.send(profilePicChangeEvent); client.send(profilePicChangeEvent);
logger.log(Level.INFO, "The user just changed his profile pic."); logger.log(Level.INFO, "The user just changed his profile pic.");
@ -178,7 +161,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
// The username was changed // The username was changed
final var validContactName = Bounds.isValidContactName(newUsername); final var validContactName = Bounds.isValidContactName(newUsername);
if (usernameChanged && validContactName) { if (usernameChanged && validContactName) {
final var nameChangeEvent = new NameChange(client.getSender().getID(), newUsername); final var nameChangeEvent = new NameChange(userID, newUsername);
eventBus.dispatch(nameChangeEvent); eventBus.dispatch(nameChangeEvent);
client.send(nameChangeEvent); client.send(nameChangeEvent);
logger.log(Level.INFO, "The user just changed his name to " + newUsername + "."); logger.log(Level.INFO, "The user just changed his name to " + newUsername + ".");
@ -195,7 +178,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
// The password was changed // The password was changed
if (validPassword) { if (validPassword) {
client.send(new PasswordChangeRequest(newPassword, oldPassword)); client.send(new PasswordChangeRequest(newPassword, oldPassword, userID));
logger.log(Level.INFO, "The user just tried to change his password!"); logger.log(Level.INFO, "The user just tried to change his password!");
} else if (!(validPassword || newPassword.isBlank())) { } else if (!(validPassword || newPassword.isBlank())) {
final var alert = new Alert(AlertType.ERROR); final var alert = new Alert(AlertType.ERROR);

View File

@ -7,7 +7,7 @@ import java.util.logging.*;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import dev.kske.eventbus.core.EventBus; import dev.kske.eventbus.EventBus;
import envoy.data.Message; import envoy.data.Message;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;

View File

@ -2,10 +2,10 @@ package envoy.client.util;
import java.util.logging.*; import java.util.logging.*;
import javafx.scene.control.*; import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import dev.kske.eventbus.core.EventBus; import dev.kske.eventbus.EventBus;
import envoy.data.*; import envoy.data.*;
import envoy.data.User.UserStatus; import envoy.data.User.UserStatus;
@ -16,7 +16,7 @@ import envoy.util.EnvoyLog;
import envoy.client.data.Context; import envoy.client.data.Context;
import envoy.client.event.*; import envoy.client.event.*;
import envoy.client.helper.*; import envoy.client.helper.*;
import envoy.client.ui.SceneInfo; import envoy.client.ui.SceneContext.SceneInfo;
import envoy.client.ui.controller.ChatScene; import envoy.client.ui.controller.ChatScene;
/** /**
@ -121,35 +121,4 @@ 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);
});
});
}
} }

View File

@ -16,13 +16,12 @@ module envoy.client {
requires javafx.fxml; requires javafx.fxml;
requires javafx.base; requires javafx.base;
requires javafx.graphics; requires javafx.graphics;
requires dev.kske.eventbus.core;
opens envoy.client.ui to javafx.graphics, javafx.fxml, dev.kske.eventbus.core; opens envoy.client.ui to javafx.graphics, javafx.fxml, dev.kske.eventbus;
opens envoy.client.ui.controller to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus.core; opens envoy.client.ui.controller to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus;
opens envoy.client.ui.chatscene to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus.core; opens envoy.client.ui.chatscene to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus;
opens envoy.client.ui.control to javafx.graphics, javafx.fxml; opens envoy.client.ui.control to javafx.graphics, javafx.fxml;
opens envoy.client.ui.settings to envoy.client.util; opens envoy.client.ui.settings to envoy.client.util;
opens envoy.client.net to dev.kske.eventbus.core; opens envoy.client.net to dev.kske.eventbus;
opens envoy.client.data to dev.kske.eventbus.core; opens envoy.client.data to dev.kske.eventbus;
} }

View File

@ -70,17 +70,6 @@
-fx-text-fill: gray; -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 { .received-message {
-fx-alignment: center-left; -fx-alignment: center-left;
-fx-background-radius: 1.3em; -fx-background-radius: 1.3em;

View File

@ -30,10 +30,6 @@
-fx-background-color: black; -fx-background-color: black;
} }
.tooltip {
-fx-text-fill: black;
}
#login-input-field { #login-input-field {
-fx-border-color: black; -fx-border-color: black;
} }

View File

@ -24,8 +24,8 @@
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" <GridPane maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="400.0" minWidth="500.0" minHeight="400.0" minWidth="500.0"
prefWidth="${screen.visualBounds.width}"
prefHeight="${screen.visualBounds.height}" prefHeight="${screen.visualBounds.height}"
prefWidth="${screen.visualBounds.width}"
xmlns="http://javafx.com/javafx/11.0.1" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="envoy.client.ui.controller.ChatScene"> fx:controller="envoy.client.ui.controller.ChatScene">
@ -57,7 +57,8 @@
<content> <content>
<AnchorPane minHeight="0.0" minWidth="0.0"> <AnchorPane minHeight="0.0" minWidth="0.0">
<children> <children>
<VBox prefHeight="3000.0" prefWidth="316.0"> <VBox prefHeight="3000.0"
prefWidth="316.0">
<children> <children>
<VBox id="search-panel" maxHeight="-Infinity" <VBox id="search-panel" maxHeight="-Infinity"
minHeight="-Infinity" prefHeight="80.0" prefWidth="316.0"> minHeight="-Infinity" prefHeight="80.0" prefWidth="316.0">
@ -155,7 +156,8 @@
<Insets left="15.0" top="5.0" right="10.0" /> <Insets left="15.0" top="5.0" right="10.0" />
</HBox.margin> </HBox.margin>
</ImageView> </ImageView>
<Region id="transparent-background" HBox.hgrow="ALWAYS" /> <Region id="transparent-background"
HBox.hgrow="ALWAYS" />
<Button fx:id="settingsButton" mnemonicParsing="false" <Button fx:id="settingsButton" mnemonicParsing="false"
onAction="#settingsButtonClicked" prefHeight="30.0" onAction="#settingsButtonClicked" prefHeight="30.0"
prefWidth="30.0" alignment="CENTER_RIGHT"> prefWidth="30.0" alignment="CENTER_RIGHT">
@ -163,7 +165,7 @@
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding> </padding>
<HBox.margin> <HBox.margin>
<Insets bottom="35.0" left="5.0" top="35.0" right="10.0" /> <Insets bottom="35.0" left="5.0" top="35.0" right="10.0"/>
</HBox.margin> </HBox.margin>
</Button> </Button>
</children> </children>

View File

@ -7,16 +7,11 @@
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<VBox alignment="TOP_RIGHT" maxHeight="-Infinity" minHeight="400.0" <VBox alignment="TOP_RIGHT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.SettingsScene">
minWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="envoy.client.ui.controller.SettingsScene">
<children> <children>
<HBox prefHeight="389.0" prefWidth="600.0"> <HBox prefHeight="389.0" prefWidth="600.0">
<children> <children>
<ListView id="message-list" fx:id="settingsList" <ListView id="message-list" fx:id="settingsList" onMouseClicked="#settingsListClicked" prefHeight="200.0" prefWidth="200.0">
onMouseClicked="#settingsListClicked" prefHeight="200.0"
prefWidth="200.0">
<opaqueInsets> <opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</opaqueInsets> </opaqueInsets>
@ -27,8 +22,7 @@
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding> </padding>
</ListView> </ListView>
<TitledPane fx:id="titledPane" collapsible="false" <TitledPane fx:id="titledPane" collapsible="false" prefHeight="400.0" prefWidth="400.0">
prefHeight="400.0" prefWidth="400.0">
<HBox.margin> <HBox.margin>
<Insets bottom="10.0" left="5.0" right="10.0" top="10.0" /> <Insets bottom="10.0" left="5.0" right="10.0" top="10.0" />
</HBox.margin> </HBox.margin>
@ -38,8 +32,7 @@
</TitledPane> </TitledPane>
</children> </children>
</HBox> </HBox>
<Button defaultButton="true" mnemonicParsing="true" <Button defaultButton="true" mnemonicParsing="true" onMouseClicked="#backButtonClicked" text="_Back">
onMouseClicked="#backButtonClicked" text="_Back">
<opaqueInsets> <opaqueInsets>
<Insets /> <Insets />
</opaqueInsets> </opaqueInsets>

View File

@ -12,11 +12,18 @@
<version>0.2-beta</version> <version>0.2-beta</version>
</parent> </parent>
<repositories>
<repository>
<id>kske-repo</id>
<url>https://kske.dev/maven-repo</url>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>dev.kske</groupId> <groupId>dev.kske</groupId>
<artifactId>event-bus-core</artifactId> <artifactId>event-bus</artifactId>
<version>1.0.0</version> <version>0.0.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>

View File

@ -1,7 +1,6 @@
package envoy.data; package envoy.data;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects;
/** /**
* This interface should be used for any type supposed to be a {@link Message} attachment (i.e. * This interface should be used for any type supposed to be a {@link Message} attachment (i.e.
@ -64,9 +63,9 @@ public final class Attachment implements Serializable {
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta
*/ */
public Attachment(byte[] data, String name, AttachmentType type) { public Attachment(byte[] data, String name, AttachmentType type) {
this.data = Objects.requireNonNull(data); this.data = data;
this.name = Objects.requireNonNull(name); this.name = name;
this.type = Objects.requireNonNull(type); this.type = type;
} }
/** /**

View File

@ -29,8 +29,8 @@ public abstract class Contact implements Serializable {
*/ */
public Contact(long id, String name, Set<? extends Contact> contacts) { public Contact(long id, String name, Set<? extends Contact> contacts) {
this.id = id; this.id = id;
this.name = Objects.requireNonNull(name); this.name = name;
this.contacts = contacts == null ? new HashSet<>() : contacts; this.contacts = contacts;
} }
/** /**

View File

@ -38,8 +38,7 @@ public final class GroupMessage extends Message {
Map<Long, MessageStatus> memberStatuses) { Map<Long, MessageStatus> memberStatuses) {
super(id, senderID, groupID, creationDate, receivedDate, readDate, text, attachment, status, super(id, senderID, groupID, creationDate, receivedDate, readDate, text, attachment, status,
forwarded); forwarded);
this.memberStatuses = this.memberStatuses = memberStatuses;
memberStatuses == null ? new HashMap<>() : memberStatuses;
} }
/** /**

View File

@ -2,13 +2,15 @@ package envoy.data;
import java.io.Serializable; import java.io.Serializable;
import dev.kske.eventbus.IEvent;
/** /**
* Generates increasing IDs between two numbers. * Generates increasing IDs between two numbers.
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha
*/ */
public final class IDGenerator implements Serializable { public final class IDGenerator implements IEvent, Serializable {
private final long end; private final long end;
private long current; private long current;

View File

@ -2,7 +2,6 @@ package envoy.data;
import java.io.Serializable; import java.io.Serializable;
import java.time.Instant; import java.time.Instant;
import java.util.Objects;
/** /**
* Contains a {@link User}'s login / registration information as well as the client version. * Contains a {@link User}'s login / registration information as well as the client version.
@ -21,14 +20,15 @@ public final class LoginCredentials implements Serializable {
private static final long serialVersionUID = 4; private static final long serialVersionUID = 4;
private LoginCredentials(String identifier, String password, boolean registration, private LoginCredentials(String identifier, String password, boolean registration,
boolean token, boolean requestToken, String clientVersion, Instant lastSync) { boolean token, boolean requestToken, String clientVersion,
this.identifier = Objects.requireNonNull(identifier); Instant lastSync) {
this.password = Objects.requireNonNull(password); this.identifier = identifier;
this.password = password;
this.registration = registration; this.registration = registration;
this.token = token; this.token = token;
this.requestToken = requestToken; this.requestToken = requestToken;
this.clientVersion = Objects.requireNonNull(clientVersion); this.clientVersion = clientVersion;
this.lastSync = lastSync == null ? Instant.EPOCH : lastSync; this.lastSync = lastSync;
} }
/** /**
@ -75,8 +75,7 @@ public final class LoginCredentials implements Serializable {
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta
*/ */
public static LoginCredentials registration(String identifier, String password, public static LoginCredentials registration(String identifier, String password,
boolean requestToken, boolean requestToken, String clientVersion, Instant lastSync) {
String clientVersion, Instant lastSync) {
return new LoginCredentials(identifier, password, true, false, requestToken, clientVersion, return new LoginCredentials(identifier, password, true, false, requestToken, clientVersion,
lastSync); lastSync);
} }

View File

@ -2,7 +2,8 @@ package envoy.data;
import java.io.Serializable; import java.io.Serializable;
import java.time.Instant; import java.time.Instant;
import java.util.Objects;
import dev.kske.eventbus.IEvent;
/** /**
* Represents a unique message with a unique, numeric ID. Further metadata includes the sender and * Represents a unique message with a unique, numeric ID. Further metadata includes the sender and
@ -12,7 +13,7 @@ import java.util.Objects;
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha
*/ */
public class Message implements Serializable { public class Message implements Serializable, IEvent {
/** /**
* This enumeration defines all possible statuses a {link Message} can have. * This enumeration defines all possible statuses a {link Message} can have.
@ -79,9 +80,9 @@ public class Message implements Serializable {
this.creationDate = creationDate; this.creationDate = creationDate;
this.receivedDate = receivedDate; this.receivedDate = receivedDate;
this.readDate = readDate; this.readDate = readDate;
this.text = text == null ? "" : text; this.text = text;
this.attachment = attachment; this.attachment = attachment;
this.status = Objects.requireNonNull(status); this.status = status;
this.forwarded = forwarded; this.forwarded = forwarded;
} }

View File

@ -86,7 +86,7 @@ public final class User extends Contact {
*/ */
public User(long id, String name, UserStatus status, Set<Contact> contacts) { public User(long id, String name, UserStatus status, Set<Contact> contacts) {
super(id, name, contacts); super(id, name, contacts);
this.status = Objects.requireNonNull(status); this.status = status;
} }
@Override @Override

View File

@ -1,33 +1,26 @@
package envoy.event; package envoy.event;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects;
import dev.kske.eventbus.IEvent;
/** /**
* This class serves as a convenience base class for all events. It provides a generic value. For * This class serves as a convenience base class for all events. It implements the {@link IEvent}
* events without a value there also is {@link envoy.event.Event.Valueless}. * interface and provides a generic value. For events without a value there also is
* {@link envoy.event.Event.Valueless}.
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @param <T> the type of the Event * @param <T> the type of the Event
* @since Envoy v0.2-alpha * @since Envoy v0.2-alpha
*/ */
public abstract class Event<T> implements Serializable { public abstract class Event<T> implements IEvent, Serializable {
protected final T value; protected final T value;
private static final long serialVersionUID = 0L; private static final long serialVersionUID = 0L;
protected Event(T value) { protected Event(T value) {
this(value, false); this.value = value;
}
/**
* This constructor is reserved for {@link Valueless} events. No other event should contain null
* values. Only use if really necessary. Using this constructor with {@code true} implies that
* the user has to manually check if the value of the event is null.
*/
protected Event(T value, boolean canBeNull) {
this.value = canBeNull ? value : Objects.requireNonNull(value);
} }
/** /**
@ -53,7 +46,7 @@ public abstract class Event<T> implements Serializable {
private static final long serialVersionUID = 0L; private static final long serialVersionUID = 0L;
protected Valueless() { protected Valueless() {
super(null, true); super(null);
} }
@Override @Override

View File

@ -20,7 +20,7 @@ public class GroupCreationResult extends Event<Group> {
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta
*/ */
public GroupCreationResult() { public GroupCreationResult() {
super(null, true); super(null);
} }
/** /**

View File

@ -30,7 +30,7 @@ public final class GroupMessageStatusChange extends MessageStatusChange {
} }
/** /**
* @return the ID of the sender of this event * @return the memberID which the user who sends this event has
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta
*/ */
public long getMemberID() { return memberID; } public long getMemberID() { return memberID; }

View File

@ -2,8 +2,6 @@ package envoy.event;
import static envoy.event.ElementOperation.*; import static envoy.event.ElementOperation.*;
import java.util.Objects;
import envoy.data.*; import envoy.data.*;
/** /**
@ -32,7 +30,7 @@ public final class GroupResize extends Event<User> {
*/ */
public GroupResize(User user, Group group, ElementOperation operation) { public GroupResize(User user, Group group, ElementOperation operation) {
super(user); super(user);
this.operation = Objects.requireNonNull(operation); this.operation = operation;
final var contained = group.getContacts().contains(user); final var contained = group.getContacts().contains(user);
if (contained && operation.equals(ADD)) if (contained && operation.equals(ADD))
throw new IllegalArgumentException(String.format("Cannot add %s to %s!", user, group)); throw new IllegalArgumentException(String.format("Cannot add %s to %s!", user, group));

View File

@ -8,6 +8,8 @@ package envoy.event;
*/ */
public final class IsTyping extends Event<Long> { public final class IsTyping extends Event<Long> {
private final long destinationID;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
@ -20,13 +22,20 @@ public final class IsTyping extends Event<Long> {
public static final int millisecondsActive = 3500; public static final int millisecondsActive = 3500;
/** /**
* Creates a new {@code IsTyping}. The client will only send the contact that should receive * Creates a new {@code IsTyping} event with originator and recipient.
* this event. The server will send the id of the contact who sent this event.
* *
* @param id the ID of the recipient (client)/ originator(server) * @param sourceID the ID of the originator
* @param destinationID the ID of the contact the user wrote to
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta
*/ */
public IsTyping(long id) { public IsTyping(Long sourceID, long destinationID) {
super(id); super(sourceID);
this.destinationID = destinationID;
} }
/**
* @return the ID of the contact in whose chat the user typed something
* @since Envoy Common v0.2-beta
*/
public long getDestinationID() { return destinationID; }
} }

View File

@ -23,7 +23,7 @@ public final class IssueProposal extends Event<String> {
*/ */
public IssueProposal(String title, String description, boolean isBug) { public IssueProposal(String title, String description, boolean isBug) {
super(escape(title)); super(escape(title));
this.description = description == null ? "" : sanitizeDescription(description); this.description = sanitizeDescription(description);
bug = isBug; bug = isBug;
} }
@ -37,8 +37,8 @@ public final class IssueProposal extends Event<String> {
*/ */
public IssueProposal(String title, String description, String user, boolean isBug) { public IssueProposal(String title, String description, String user, boolean isBug) {
super(escape(title)); super(escape(title));
this.description = description == null ? "" this.description =
: sanitizeDescription(description) + String.format("<br>Submitted by user %s.", user); sanitizeDescription(description) + String.format("<br>Submitted by user %s.", user);
bug = isBug; bug = isBug;
} }

View File

@ -1,7 +1,6 @@
package envoy.event; package envoy.event;
import java.time.Instant; import java.time.Instant;
import java.util.Objects;
import envoy.data.Message; import envoy.data.Message;
@ -27,7 +26,7 @@ public class MessageStatusChange extends Event<Message.MessageStatus> {
public MessageStatusChange(long id, Message.MessageStatus status, Instant date) { public MessageStatusChange(long id, Message.MessageStatus status, Instant date) {
super(status); super(status);
this.id = id; this.id = id;
this.date = Objects.requireNonNull(date); this.date = date;
} }
/** /**

View File

@ -1,6 +1,6 @@
package envoy.event; package envoy.event;
import java.util.Objects; import envoy.data.Contact;
/** /**
* @author Leon Hofmeister * @author Leon Hofmeister
@ -8,20 +8,29 @@ import java.util.Objects;
*/ */
public final class PasswordChangeRequest extends Event<String> { public final class PasswordChangeRequest extends Event<String> {
private final String oldPassword; private final long id;
private final String oldPassword;
private static final long serialVersionUID = 0L; private static final long serialVersionUID = 0L;
/** /**
* @param newPassword the new password of that user * @param newPassword the new password of that user
* @param oldPassword the old password of that user * @param oldPassword the old password of that user
* @param userID the ID of the user who wants to change his password
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta
*/ */
public PasswordChangeRequest(String newPassword, String oldPassword) { public PasswordChangeRequest(String newPassword, String oldPassword, long userID) {
super(newPassword); super(newPassword);
this.oldPassword = Objects.requireNonNull(oldPassword); this.oldPassword = oldPassword;
id = userID;
} }
/**
* @return the ID of the {@link Contact} this event is related to
* @since Envoy Common v0.2-alpha
*/
public long getID() { return id; }
/** /**
* @return the old password of the underlying user * @return the old password of the underlying user
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta
@ -30,6 +39,6 @@ public final class PasswordChangeRequest extends Event<String> {
@Override @Override
public String toString() { public String toString() {
return "PasswordChangeRequest[]"; return "PasswordChangeRequest[id=" + id + "]";
} }
} }

View File

@ -6,13 +6,23 @@ package envoy.event;
*/ */
public final class ProfilePicChange extends Event<byte[]> { public final class ProfilePicChange extends Event<byte[]> {
private final long id;
private static final long serialVersionUID = 0L; private static final long serialVersionUID = 0L;
/** /**
* @param value the byte[] of the new image * @param value the byte[] of the new image
* @param userID the ID of the user who changed his profile pic
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta
*/ */
public ProfilePicChange(byte[] value) { public ProfilePicChange(byte[] value, long userID) {
super(value); super(value);
id = userID;
} }
/**
* @return the ID of the user changing his profile pic
* @since Envoy Common v0.2-beta
*/
public long getId() { return id; }
} }

View File

@ -1,7 +1,5 @@
package envoy.event.contact; package envoy.event.contact;
import java.util.Objects;
import envoy.data.User; import envoy.data.User;
import envoy.event.*; import envoy.event.*;
@ -26,7 +24,7 @@ public final class UserOperation extends Event<User> {
*/ */
public UserOperation(User contact, ElementOperation operationType) { public UserOperation(User contact, ElementOperation operationType) {
super(contact); super(contact);
this.operationType = Objects.requireNonNull(operationType); this.operationType = operationType;
} }
/** /**

View File

@ -16,5 +16,5 @@ module envoy.common {
exports envoy.event.contact; exports envoy.event.contact;
requires transitive java.logging; requires transitive java.logging;
requires transitive dev.kske.eventbus.core; requires transitive dev.kske.eventbus;
} }

View File

@ -18,10 +18,10 @@ import envoy.util.SerializationUtils;
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta
*/ */
public class UserTest { class UserTest {
@Test @Test
public void test() throws IOException, ClassNotFoundException { void test() throws IOException, ClassNotFoundException {
User user2 = new User(2, "kai"); User user2 = new User(2, "kai");
User user3 = new User(3, "ai"); User user3 = new User(3, "ai");
User user4 = new User(4, "ki", Set.of(user2, user3)); User user4 = new User(4, "ki", Set.of(user2, user3));

285
doc/datamodel.uxf Normal file
View File

@ -0,0 +1,285 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<diagram program="umlet" version="14.3.0">
<zoom_level>10</zoom_level>
<element>
<id>UMLClass</id>
<coordinates>
<x>50</x>
<y>60</y>
<w>210</w>
<h>240</h>
</coordinates>
<panel_attributes>DBUser
--
id: Long
nick: String
name: String
chats: Set&lt;DBChat&gt;
status: UserStatus
created: Instant
lastSeen: Instant
deleted: Boolean
avatar: DBAvatar
passwordHash: String
authToken: String
authTokenExpiration: Instant
--
+toCommon(): User</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLClass</id>
<coordinates>
<x>380</x>
<y>330</y>
<w>290</w>
<h>160</h>
</coordinates>
<panel_attributes>DBMessage
--
id: Long
sender: DBUser
forwarded: Boolean
deleted: Boolean
statuses: Map&lt;DBUser, DBMessageStatus&gt;
text: String
attachment: DBAttachment
--
+toCommon(): Message</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLClass</id>
<coordinates>
<x>770</x>
<y>320</y>
<w>210</w>
<h>100</h>
</coordinates>
<panel_attributes>DBMessageStatus
--
id: Long
status: MessageStatus
timestamp: Instant
--
+toCommon(): MessageStatus</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLClass</id>
<coordinates>
<x>770</x>
<y>440</y>
<w>180</w>
<h>120</h>
</coordinates>
<panel_attributes>DBAttachment
--
id: Long
name: String
type: AttachmentType
url: String
--
+toCommon(): Attachment</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLClass</id>
<coordinates>
<x>510</x>
<y>70</y>
<w>200</w>
<h>180</h>
</coordinates>
<panel_attributes>DBChat
--
id: Long
name: String
members: Set&lt;DBUser&gt;
admins: Set&lt;DBUser&gt;
messages: Set&lt;DBMessage&gt;
avatar: DBAvatar
allowJoining: Boolean
allowAttachments: Boolean
--
+toCommon(): Chat</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>250</x>
<y>100</y>
<w>280</w>
<h>40</h>
</coordinates>
<panel_attributes>lt=&lt;-&gt;
m1=0..n
m2=0..n</panel_attributes>
<additional_attributes>10.0;10.0;260.0;10.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>600</x>
<y>240</y>
<w>50</w>
<h>110</h>
</coordinates>
<panel_attributes>lt=-&gt;
m2=0..n</panel_attributes>
<additional_attributes>10.0;10.0;10.0;90.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>660</x>
<y>360</y>
<w>130</w>
<h>40</h>
</coordinates>
<panel_attributes>lt=-&gt;
m2=0..n</panel_attributes>
<additional_attributes>10.0;10.0;110.0;10.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>660</x>
<y>470</y>
<w>130</w>
<h>40</h>
</coordinates>
<panel_attributes>lt=-&gt;
m2=0..1</panel_attributes>
<additional_attributes>10.0;10.0;110.0;10.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>140</x>
<y>290</y>
<w>260</w>
<h>150</h>
</coordinates>
<panel_attributes>lt=&lt;-
m1=0..n</panel_attributes>
<additional_attributes>10.0;10.0;10.0;130.0;240.0;130.0</additional_attributes>
</element>
<element>
<id>UMLPackage</id>
<coordinates>
<x>10</x>
<y>0</y>
<w>1010</w>
<h>590</h>
</coordinates>
<panel_attributes>envoy.server.data
--
bg=#CCCCCC</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLClass</id>
<coordinates>
<x>90</x>
<y>680</y>
<w>170</w>
<h>130</h>
</coordinates>
<panel_attributes>User
--
id: long
nick: String
name: String
status: UserStatus
created: Instant
deleted: boolean</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLClass</id>
<coordinates>
<x>340</x>
<y>680</y>
<w>190</w>
<h>100</h>
</coordinates>
<panel_attributes>UserStatus
--
_+ONLINE: UserStatus_
_+AWAY: UserStatus_
_+BUSY: UserStatus_
_+OFFLINE: UserStatus_</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>250</x>
<y>730</y>
<w>110</w>
<h>30</h>
</coordinates>
<panel_attributes>lt=&lt;-</panel_attributes>
<additional_attributes>90.0;10.0;10.0;10.0</additional_attributes>
</element>
<element>
<id>UMLClass</id>
<coordinates>
<x>340</x>
<y>150</y>
<w>100</w>
<h>80</h>
</coordinates>
<panel_attributes>DBAvatar
--
id: Long
edited: Instant
url: String
layer=1</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>250</x>
<y>190</y>
<w>110</w>
<h>40</h>
</coordinates>
<panel_attributes>lt=&lt;-
m1=0..1</panel_attributes>
<additional_attributes>90.0;10.0;10.0;10.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>430</x>
<y>190</y>
<w>100</w>
<h>40</h>
</coordinates>
<panel_attributes>lt=&lt;-
m1=0..1</panel_attributes>
<additional_attributes>10.0;10.0;80.0;10.0</additional_attributes>
</element>
<element>
<id>UMLClass</id>
<coordinates>
<x>610</x>
<y>680</y>
<w>180</w>
<h>130</h>
</coordinates>
<panel_attributes>Chat
--
id: long
name: String
deceased: boolean
members: Set&lt;User&gt;
admins: Set&lt;User&gt;
messages: List&lt;Message&gt;</panel_attributes>
<additional_attributes/>
</element>
</diagram>

View File

@ -0,0 +1,501 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<diagram program="umlet" version="14.3.0">
<zoom_level>9</zoom_level>
<element>
<id>UMLActor</id>
<coordinates>
<x>72</x>
<y>540</y>
<w>54</w>
<h>99</h>
</coordinates>
<panel_attributes>Client1</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>2196</x>
<y>504</y>
<w>198</w>
<h>171</h>
</coordinates>
<panel_attributes>Database
halign=center
bg=orange
--</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>351</x>
<y>513</y>
<w>90</w>
<h>117</h>
</coordinates>
<panel_attributes>Main</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>531</x>
<y>513</y>
<w>90</w>
<h>117</h>
</coordinates>
<panel_attributes>Startup
bg=orange</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>702</x>
<y>513</y>
<w>90</w>
<h>117</h>
</coordinates>
<panel_attributes>Client
bg=yellow</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>873</x>
<y>513</y>
<w>90</w>
<h>117</h>
</coordinates>
<panel_attributes>LocalDB
bg=pink</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>1548</x>
<y>522</y>
<w>189</w>
<h>117</h>
</coordinates>
<panel_attributes>LoginCredentialsProcessor
bg=cyan</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>1809</x>
<y>522</y>
<w>135</w>
<h>117</h>
</coordinates>
<panel_attributes>PersistenceManager
bg=gray</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>396</x>
<y>729</y>
<w>189</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=&lt;&lt;-
start javaFX app</panel_attributes>
<additional_attributes>190.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>387</x>
<y>720</y>
<w>18</w>
<h>54</h>
</coordinates>
<panel_attributes/>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>387</x>
<y>621</y>
<w>27</w>
<h>117</h>
</coordinates>
<panel_attributes>lt=.</panel_attributes>
<additional_attributes>10.0;10.0;10.0;110.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>567</x>
<y>621</y>
<w>27</w>
<h>117</h>
</coordinates>
<panel_attributes>lt=.</panel_attributes>
<additional_attributes>10.0;10.0;10.0;110.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>738</x>
<y>621</y>
<w>27</w>
<h>216</h>
</coordinates>
<panel_attributes>lt=.</panel_attributes>
<additional_attributes>10.0;10.0;10.0;220.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>909</x>
<y>621</y>
<w>27</w>
<h>153</h>
</coordinates>
<panel_attributes>lt=.</panel_attributes>
<additional_attributes>10.0;10.0;10.0;150.0</additional_attributes>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>567</x>
<y>720</y>
<w>18</w>
<h>396</h>
</coordinates>
<panel_attributes>bg=orange</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>738</x>
<y>819</y>
<w>18</w>
<h>558</h>
</coordinates>
<panel_attributes>bg=yellow</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>909</x>
<y>756</y>
<w>18</w>
<h>621</h>
</coordinates>
<panel_attributes>bg=pink</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>576</x>
<y>756</y>
<w>351</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=&lt;&lt;-
initialize</panel_attributes>
<additional_attributes>370.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>1062</x>
<y>513</y>
<w>90</w>
<h>117</h>
</coordinates>
<panel_attributes>Receiver
bg=red</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>1098</x>
<y>621</y>
<w>27</w>
<h>270</h>
</coordinates>
<panel_attributes>lt=.</panel_attributes>
<additional_attributes>10.0;10.0;10.0;280.0</additional_attributes>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>1098</x>
<y>873</y>
<w>18</w>
<h>504</h>
</coordinates>
<panel_attributes>bg=red</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>576</x>
<y>1071</y>
<w>180</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=&lt;&lt;-
init receivers
layer=0</panel_attributes>
<additional_attributes>180.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>576</x>
<y>828</y>
<w>180</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=&lt;&lt;-
perform handshake</panel_attributes>
<additional_attributes>180.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>747</x>
<y>873</y>
<w>369</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=&lt;&lt;-
init handhake receivers
layer=0</panel_attributes>
<additional_attributes>390.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>747</x>
<y>1071</y>
<w>369</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=&lt;&lt;-
init permanent receivers
layer=0</panel_attributes>
<additional_attributes>390.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>1638</x>
<y>630</y>
<w>27</w>
<h>306</h>
</coordinates>
<panel_attributes>lt=.</panel_attributes>
<additional_attributes>10.0;10.0;10.0;320.0</additional_attributes>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>1638</x>
<y>918</y>
<w>18</w>
<h>558</h>
</coordinates>
<panel_attributes>bg=cyan</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>747</x>
<y>918</y>
<w>909</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=&lt;&lt;-
send login credentials
layer=0</panel_attributes>
<additional_attributes>990.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>1872</x>
<y>630</y>
<w>27</w>
<h>306</h>
</coordinates>
<panel_attributes>lt=.</panel_attributes>
<additional_attributes>10.0;10.0;10.0;320.0</additional_attributes>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>1872</x>
<y>918</y>
<w>18</w>
<h>558</h>
</coordinates>
<panel_attributes>bg=gray</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>1881</x>
<y>918</y>
<w>423</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=&lt;&lt;-
use queries
layer=0</panel_attributes>
<additional_attributes>450.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>2286</x>
<y>666</y>
<w>27</w>
<h>270</h>
</coordinates>
<panel_attributes>lt=.</panel_attributes>
<additional_attributes>10.0;10.0;10.0;280.0</additional_attributes>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>2286</x>
<y>918</y>
<w>18</w>
<h>558</h>
</coordinates>
<panel_attributes>bg=orange</panel_attributes>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>1647</x>
<y>918</y>
<w>243</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=&lt;&lt;-
aquire data
layer=0</panel_attributes>
<additional_attributes>250.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>1881</x>
<y>954</y>
<w>423</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=-&gt;&gt;
send query responses
layer=0</panel_attributes>
<additional_attributes>450.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>1647</x>
<y>954</y>
<w>243</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=-&gt;&gt;
send aquired data
layer=0</panel_attributes>
<additional_attributes>250.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>1107</x>
<y>990</y>
<w>549</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=-&gt;&gt;
send user, chats, pending messages, etc.
layer=0</panel_attributes>
<additional_attributes>590.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>918</x>
<y>990</y>
<w>198</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=-&gt;&gt;
store data
layer=0</panel_attributes>
<additional_attributes>200.0;20.0;10.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>99</x>
<y>729</y>
<w>306</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=-&gt;
start app</panel_attributes>
<additional_attributes>10.0;20.0;320.0;20.0</additional_attributes>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>90</x>
<y>621</y>
<w>27</w>
<h>117</h>
</coordinates>
<panel_attributes>lt=.</panel_attributes>
<additional_attributes>10.0;10.0;10.0;110.0</additional_attributes>
</element>
<element>
<id>UMLGeneric</id>
<coordinates>
<x>90</x>
<y>720</y>
<w>18</w>
<h>54</h>
</coordinates>
<panel_attributes/>
<additional_attributes/>
</element>
<element>
<id>Relation</id>
<coordinates>
<x>747</x>
<y>1026</y>
<w>369</w>
<h>36</h>
</coordinates>
<panel_attributes>lt=-&gt;&gt;
online
layer=0</panel_attributes>
<additional_attributes>390.0;20.0;10.0;20.0</additional_attributes>
</element>
</diagram>

View File

@ -28,13 +28,6 @@
</plugin> </plugin>
</plugins> </plugins>
</pluginManagement> </pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
</plugins>
</build> </build>
<modules> <modules>

View File

@ -1,7 +1,7 @@
package envoy.server.data; package envoy.server.data;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import javax.persistence.*; import javax.persistence.*;
@ -121,9 +121,8 @@ public final class PersistenceManager {
transaction(() -> { transaction(() -> {
// Remove this contact from the contact list of his contacts // Remove this contact from the contact list of his contacts
for (final var remainingContact : contact.contacts) for (final var remainingContact : contact.getContacts())
remainingContact.getContacts().remove(contact); remainingContact.getContacts().remove(contact);
contact.contacts.clear();
}); });
remove(contact); remove(contact);
} }
@ -224,9 +223,6 @@ public final class PersistenceManager {
* @since Envoy Server Standalone v0.2-beta * @since Envoy Server Standalone v0.2-beta
*/ */
public List<Message> getPendingMessages(User user, Instant lastSync) { public List<Message> getPendingMessages(User user, Instant lastSync) {
if (user == null)
return new ArrayList<>();
lastSync = Objects.requireNonNullElse(lastSync, Instant.EPOCH);
return entityManager.createNamedQuery(Message.getPending).setParameter("user", user) return entityManager.createNamedQuery(Message.getPending).setParameter("user", user)
.setParameter("lastSeen", lastSync).getResultList(); .setParameter("lastSeen", lastSync).getResultList();
} }
@ -240,9 +236,6 @@ public final class PersistenceManager {
* @since Envoy Server Standalone v0.2-beta * @since Envoy Server Standalone v0.2-beta
*/ */
public List<GroupMessage> getPendingGroupMessages(User user, Instant lastSync) { public List<GroupMessage> getPendingGroupMessages(User user, Instant lastSync) {
if (user == null)
return new ArrayList<>();
lastSync = Objects.requireNonNullElse(lastSync, Instant.EPOCH);
return entityManager.createNamedQuery(GroupMessage.getPendingGroupMsg) return entityManager.createNamedQuery(GroupMessage.getPendingGroupMsg)
.setParameter("userId", user.getID()) .setParameter("userId", user.getID())
.setParameter("lastSeen", lastSync) .setParameter("lastSeen", lastSync)
@ -284,18 +277,16 @@ public final class PersistenceManager {
* @since Envoy Server v0.3-beta * @since Envoy Server v0.3-beta
*/ */
public void addContactBidirectional(Contact contact1, Contact contact2) { public void addContactBidirectional(Contact contact1, Contact contact2) {
if (!(contact1 == null || contact2 == null)) {
// Add users to each others contact list // Add users to each others contact list
contact1.getContacts().add(contact2); contact1.getContacts().add(contact2);
contact2.getContacts().add(contact1); contact2.getContacts().add(contact1);
// Synchronize changes with the database // Synchronize changes with the database
transaction(() -> { transaction(() -> {
entityManager.merge(contact1); entityManager.merge(contact1);
entityManager.merge(contact2); entityManager.merge(contact2);
}); });
}
} }
/** /**
@ -317,18 +308,16 @@ public final class PersistenceManager {
* @since Envoy Server v0.3-beta * @since Envoy Server v0.3-beta
*/ */
public void removeContactBidirectional(Contact contact1, Contact contact2) { public void removeContactBidirectional(Contact contact1, Contact contact2) {
if (!(contact1 == null || contact2 == null)) {
// Remove users from each others contact list // Remove users from each others contact list
contact1.getContacts().remove(contact2); contact1.getContacts().remove(contact2);
contact2.getContacts().remove(contact1); contact2.getContacts().remove(contact1);
// Synchronize changes with the database // Synchronize changes with the database
transaction(() -> { transaction(() -> {
entityManager.merge(contact1); entityManager.merge(contact1);
entityManager.merge(contact2); entityManager.merge(contact2);
}); });
}
} }
/** /**
@ -342,36 +331,15 @@ public final class PersistenceManager {
} }
private void persist(Object obj) { private void persist(Object obj) {
try { transaction(() -> entityManager.persist(obj));
transaction(() -> entityManager.persist(obj));
} catch (EntityExistsException e) {
if (transaction.isActive())
transaction.rollback();
EnvoyLog.getLogger(PersistenceManager.class).log(Level.WARNING,
String.format("Could not persist %s: entity exists already.", obj));
}
} }
private void merge(Object obj) { private void merge(Object obj) {
try { transaction(() -> entityManager.merge(obj));
transaction(() -> entityManager.merge(obj));
} catch (IllegalArgumentException e) {
if (transaction.isActive())
transaction.rollback();
EnvoyLog.getLogger(PersistenceManager.class).log(Level.WARNING,
String.format("Could not merge %s: entity doesn't exist.", obj));
}
} }
private void remove(Object obj) { private void remove(Object obj) {
try { transaction(() -> entityManager.remove(obj));
transaction(() -> entityManager.remove(obj));
} catch (IllegalArgumentException e) {
if (transaction.isActive())
transaction.rollback();
EnvoyLog.getLogger(PersistenceManager.class).log(Level.WARNING,
String.format("Could not remove %s: entity didn't exist (for the database).", obj));
}
} }
/** /**

View File

@ -49,10 +49,8 @@ public final class ConnectionManager implements ISocketIdListener {
// Notify contacts of this users offline-going // Notify contacts of this users offline-going
final envoy.server.data.User user = final envoy.server.data.User user =
PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID)); PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID));
if (user != null) { user.setLastSeen(Instant.now());
user.setLastSeen(Instant.now()); UserStatusChangeProcessor.updateUserStatus(user, UserStatus.OFFLINE);
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

@ -33,6 +33,7 @@ public final class ObjectMessageProcessor implements IMessageProcessor {
this.processors = processors; this.processors = processors;
} }
@SuppressWarnings("unchecked")
@Override @Override
public void process(Message message, WriteProxy writeProxy) { public void process(Message message, WriteProxy writeProxy) {
try (ObjectInputStream in = try (ObjectInputStream in =
@ -44,34 +45,23 @@ public final class ObjectMessageProcessor implements IMessageProcessor {
return; return;
} }
logger.log(Level.INFO, "Received " + obj); logger.fine("Received " + obj);
refer(message.socketId, writeProxy, obj); // Get processor and input class and process object
for (@SuppressWarnings("rawtypes")
ObjectProcessor p : processors) {
Class<?> c = (Class<?>) ((ParameterizedType) p.getClass().getGenericInterfaces()[0])
.getActualTypeArguments()[0];
if (c.equals(obj.getClass()))
try {
p.process(c.cast(obj), message.socketId, new ObjectWriteProxy(writeProxy));
break;
} catch (IOException e) {
logger.log(Level.SEVERE, "Exception during processor execution: ", e);
}
}
} catch (IOException | ClassNotFoundException e) { } catch (IOException | ClassNotFoundException e) {
logger.log(Level.WARNING, e.printStackTrace();
"An exception occurred when reading in an object: " + e);
}
}
/**
* Executes the appropriate {@link ObjectProcessor} for the given input ({@code obj}), if any is
* present.
*/
@SuppressWarnings("unchecked")
private void refer(long socketID, WriteProxy writeProxy, Object obj) {
// Get processor and input class and process object
for (@SuppressWarnings("rawtypes")
ObjectProcessor p : processors) {
Class<?> c = (Class<?>) ((ParameterizedType) p.getClass().getGenericInterfaces()[0])
.getActualTypeArguments()[0];
if (c.equals(obj.getClass()))
try {
p.process(c.cast(obj), socketID, new ObjectWriteProxy(writeProxy));
break;
} catch (IOException e) {
logger.log(Level.SEVERE, "Exception during processor execution: ", e);
}
} }
} }
} }

View File

@ -5,7 +5,7 @@ import static envoy.server.Startup.config;
import java.time.Instant; import java.time.Instant;
import java.util.Collections; import java.util.Collections;
import java.util.logging.*; import java.util.logging.Logger;
import javax.persistence.EntityExistsException; import javax.persistence.EntityExistsException;
@ -15,7 +15,6 @@ import envoy.util.EnvoyLog;
import envoy.server.data.PersistenceManager; import envoy.server.data.PersistenceManager;
import envoy.server.net.*; import envoy.server.net.*;
import envoy.server.util.UserAuthenticationUtil;
/** /**
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
@ -30,15 +29,6 @@ public final class GroupMessageProcessor implements ObjectProcessor<GroupMessage
@Override @Override
public void process(GroupMessage groupMessage, long socketID, ObjectWriteProxy writeProxy) { public void process(GroupMessage groupMessage, long socketID, ObjectWriteProxy writeProxy) {
// Check whether the message has the expected parameters
if (!UserAuthenticationUtil.isExpectedUser(groupMessage.getSenderID(), socketID)
|| persistenceManager.getContactByID(groupMessage.getRecipientID()) == null) {
logger.log(Level.INFO,
"Received a group message with invalid parameters");
return;
}
groupMessage.nextStatus(); groupMessage.nextStatus();
// Update statuses to SENT / RECEIVED depending on online status // Update statuses to SENT / RECEIVED depending on online status

View File

@ -12,7 +12,6 @@ import envoy.util.EnvoyLog;
import envoy.server.data.*; import envoy.server.data.*;
import envoy.server.net.*; import envoy.server.net.*;
import envoy.server.util.UserAuthenticationUtil;
/** /**
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
@ -29,17 +28,7 @@ public final class GroupMessageStatusChangeProcessor
@Override @Override
public void process(GroupMessageStatusChange statusChange, long socketID, public void process(GroupMessageStatusChange statusChange, long socketID,
ObjectWriteProxy writeProxy) { ObjectWriteProxy writeProxy) {
// Check whether the message has the expected parameters
if (!UserAuthenticationUtil.isExpectedUser(statusChange.getMemberID(), socketID)) {
logger.log(Level.INFO,
"Received a group message with invalid parameters");
return;
}
GroupMessage gmsg = (GroupMessage) persistenceManager.getMessageByID(statusChange.getID()); 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 // Any other status than READ is not supposed to be sent to the server
if (statusChange.get() != MessageStatus.READ) { if (statusChange.get() != MessageStatus.READ) {

View File

@ -24,10 +24,6 @@ public final class GroupResizeProcessor implements ObjectProcessor<GroupResize>
final var group = persistenceManager.getGroupByID(groupResize.getGroupID()); final var group = persistenceManager.getGroupByID(groupResize.getGroupID());
final var sender = persistenceManager.getUserByID(groupResize.get().getID()); 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 // Perform the desired operation
switch (groupResize.getOperation()) { switch (groupResize.getOperation()) {
case ADD: case ADD:

View File

@ -23,11 +23,10 @@ public final class IsTypingProcessor implements ObjectProcessor<IsTyping> {
throws IOException { throws IOException {
final var contact = persistenceManager.getContactByID(event.get()); final var contact = persistenceManager.getContactByID(event.get());
if (contact instanceof User) { if (contact instanceof User) {
if (connectionManager.isOnline(event.get())) final var destinationID = event.getDestinationID();
writeProxy.write(connectionManager.getSocketID(event.get()), if (connectionManager.isOnline(destinationID))
new IsTyping(connectionManager.getUserIDBySocketID(socketID))); writeProxy.write(connectionManager.getSocketID(destinationID), event);
} else } else
writeProxy.writeToOnlineContacts(contact.getContacts(), writeProxy.writeToOnlineContacts(contact.getContacts(), event);
new IsTyping(connectionManager.getUserIDBySocketID(socketID)));
} }
} }

View File

@ -40,7 +40,6 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
// Cache this write proxy for user-independent notifications // Cache this write proxy for user-independent notifications
UserStatusChangeProcessor.setWriteProxy(writeProxy); UserStatusChangeProcessor.setWriteProxy(writeProxy);
// Check for compatible versions
if (!VersionUtil.verifyCompatibility(credentials.getClientVersion())) { if (!VersionUtil.verifyCompatibility(credentials.getClientVersion())) {
logger.info("The client has the wrong version."); logger.info("The client has the wrong version.");
writeProxy.write(socketID, new HandshakeRejection(WRONG_VERSION)); writeProxy.write(socketID, new HandshakeRejection(WRONG_VERSION));
@ -71,10 +70,10 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN)); writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN));
return; return;
} }
} else if (!PasswordUtil.validate(credentials.getPassword(), } else
user.getPasswordHash())) {
// Check the password hash // Check the password hash
if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
logger.info(user + " has entered the wrong password."); logger.info(user + " has entered the wrong password.");
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER)); writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
return; return;
@ -102,8 +101,7 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
writeProxy.write(socketID, new HandshakeRejection(USERNAME_TAKEN)); writeProxy.write(socketID, new HandshakeRejection(USERNAME_TAKEN));
return; return;
} catch (final NoResultException e) { } catch (final NoResultException e) {
// Creation of a new user
// Create a new user
user = new User(); user = new User();
user.setName(credentials.getIdentifier()); user.setName(credentials.getIdentifier());
user.setLastSeen(Instant.now()); user.setLastSeen(Instant.now());
@ -125,6 +123,7 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
// 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
@ -141,14 +140,6 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
writeProxy.write(socketID, new NewAuthToken(token)); writeProxy.write(socketID, new NewAuthToken(token));
} }
// Notify the user if a contact deletion has happened since he last logged in
if (user.getLatestContactDeletion().isAfter(user.getLastSeen()))
writeProxy.write(socketID, new ContactsChangedSinceLastLogin());
// Complete the handshake
writeProxy.write(socketID, user.toCommon());
// Send pending (group) messages and status changes
final var pendingMessages = final var pendingMessages =
PersistenceManager.getInstance().getPendingMessages(user, credentials.getLastSync()); PersistenceManager.getInstance().getPendingMessages(user, credentials.getLastSync());
pendingMessages.removeIf(GroupMessage.class::isInstance); pendingMessages.removeIf(GroupMessage.class::isInstance);
@ -173,9 +164,8 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
writeProxy.write(connectionManager.getSocketID(msg.getSender().getID()), writeProxy.write(connectionManager.getSocketID(msg.getSender().getID()),
new MessageStatusChange(msgCommon)); new MessageStatusChange(msgCommon));
} }
} else { } else
writeProxy.write(socketID, new MessageStatusChange(msgCommon)); writeProxy.write(socketID, new MessageStatusChange(msgCommon));
}
} }
final List<GroupMessage> pendingGroupMessages = PersistenceManager.getInstance() final List<GroupMessage> pendingGroupMessages = PersistenceManager.getInstance()
@ -209,11 +199,10 @@ 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
@ -233,5 +222,11 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
writeProxy.write(socketID, new MessageStatusChange(gmsgCommon)); writeProxy.write(socketID, new MessageStatusChange(gmsgCommon));
} }
} }
// Notify the user if a contact deletion has happened since he last logged in
if (user.getLatestContactDeletion().isAfter(user.getLastSeen()))
writeProxy.write(socketID, new ContactsChangedSinceLastLogin());
// Complete the handshake
writeProxy.write(socketID, user.toCommon());
} }
} }

View File

@ -12,7 +12,6 @@ import envoy.util.EnvoyLog;
import envoy.server.data.PersistenceManager; import envoy.server.data.PersistenceManager;
import envoy.server.net.*; import envoy.server.net.*;
import envoy.server.util.UserAuthenticationUtil;
/** /**
* This {@link ObjectProcessor} handles incoming {@link Message}s. * This {@link ObjectProcessor} handles incoming {@link Message}s.
@ -30,15 +29,6 @@ public final class MessageProcessor implements ObjectProcessor<Message> {
@Override @Override
public void process(Message message, long socketID, ObjectWriteProxy writeProxy) { public void process(Message message, long socketID, ObjectWriteProxy writeProxy) {
// Check whether the message has the expected parameters
if (!UserAuthenticationUtil.isExpectedUser(message.getSenderID(), socketID)
|| persistenceManager.getContactByID(message.getRecipientID()) == null) {
logger.log(Level.INFO,
"Received a message with invalid parameters");
return;
}
message.nextStatus(); message.nextStatus();
// Convert to server message // Convert to server message

View File

@ -32,8 +32,6 @@ public final class MessageStatusChangeProcessor implements ObjectProcessor<Messa
} }
final var msg = persistenceManager.getMessageByID(statusChange.getID()); final var msg = persistenceManager.getMessageByID(statusChange.getID());
if (msg == null)
return;
msg.read(); msg.read();
persistenceManager.updateMessage(msg); persistenceManager.updateMessage(msg);

View File

@ -7,7 +7,7 @@ import envoy.event.*;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
import envoy.server.data.PersistenceManager; import envoy.server.data.PersistenceManager;
import envoy.server.net.*; import envoy.server.net.ObjectWriteProxy;
import envoy.server.util.PasswordUtil; import envoy.server.util.PasswordUtil;
/** /**
@ -21,8 +21,7 @@ public final class PasswordChangeRequestProcessor
public void process(PasswordChangeRequest event, long socketID, ObjectWriteProxy writeProxy) public void process(PasswordChangeRequest event, long socketID, ObjectWriteProxy writeProxy)
throws IOException { throws IOException {
final var persistenceManager = PersistenceManager.getInstance(); final var persistenceManager = PersistenceManager.getInstance();
final var user = persistenceManager final var user = persistenceManager.getUserByID(event.getID());
.getUserByID(ConnectionManager.getInstance().getUserIDBySocketID(socketID));
final var logger = final var logger =
EnvoyLog.getLogger(PasswordChangeRequestProcessor.class); EnvoyLog.getLogger(PasswordChangeRequestProcessor.class);
final var correctAuthentication = final var correctAuthentication =

View File

@ -22,16 +22,10 @@ public final class UserOperationProcessor implements ObjectProcessor<UserOperati
private static final PersistenceManager persistenceManager = PersistenceManager.getInstance(); private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
@Override @Override
public void process(UserOperation evt, long socketID, ObjectWriteProxy writeProxy) { public void process(UserOperation evt, long socketId, ObjectWriteProxy writeProxy) {
final long userID = ConnectionManager.getInstance().getUserIDBySocketID(socketID); final long userID = ConnectionManager.getInstance().getUserIDBySocketID(socketId);
final long contactID = evt.get().getID(); final long contactID = evt.get().getID();
final var recipient = persistenceManager.getUserByID(contactID); final var sender = persistenceManager.getUserByID(userID);
// 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()) { switch (evt.getOperationType()) {
case ADD: case ADD:
logger.log(Level.FINE, logger.log(Level.FINE,
@ -51,7 +45,7 @@ public final class UserOperationProcessor implements ObjectProcessor<UserOperati
sender.setLatestContactDeletion(Instant.now()); sender.setLatestContactDeletion(Instant.now());
// Notify the removed contact on next startup(s) of this deletion // Notify the removed contact on next startup(s) of this deletion
recipient.setLatestContactDeletion(Instant.now()); persistenceManager.getUserByID(contactID).setLatestContactDeletion(Instant.now());
// Notify the removed contact if online // Notify the removed contact if online
if (connectionManager.isOnline(contactID)) if (connectionManager.isOnline(contactID))

View File

@ -1,24 +0,0 @@
package envoy.server.util;
import envoy.server.net.ConnectionManager;
/**
* @author Leon Hofmeister
* @since Envoy Server v0.3-beta
*/
public final class UserAuthenticationUtil {
private UserAuthenticationUtil() {}
/**
* Checks whether a user is really who he claims to be.
*
* @param expectedID the expected user ID
* @param socketID the socket ID of the user making a request
* @return whether this user is who he claims to be
* @since Envoy Server v0.3-beta
*/
public static boolean isExpectedUser(long expectedID, long socketID) {
return ConnectionManager.getInstance().getUserIDBySocketID(socketID) == expectedID;
}
}