Compare commits
26 Commits
f/fancy-di
...
develop
Author | SHA1 | Date | |
---|---|---|---|
6499a4f698 | |||
05ed5da41b | |||
c5f4969666 | |||
1a9f9a85ab | |||
544210a811 | |||
5ef5d96445 | |||
dcf1b0c58d | |||
10213a0d3d | |||
b653652f6d | |||
0ff910ebde | |||
6d85e337d2 | |||
67ebc6be83 | |||
e3052a2133 | |||
4d4865570d | |||
0ce8b0c89d | |||
cd7793a589 | |||
e5659c1da1 | |||
f67ca1d61d | |||
8bdd201b28 | |||
f6c772a655 | |||
7a883861be | |||
d4c7813c97 | |||
889e9b186f | |||
fccd7e70b1 | |||
2eeb55ed52 | |||
44d3082958 |
37
Jenkinsfile
vendored
Normal file
37
Jenkinsfile
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@ 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.
|
||||
|
||||
Additional info on how to use Envoy can be found [here](https://git.kske.dev/zdm/envoy/wiki) in the client section.
|
||||
|
||||
### System requirements
|
||||
|
||||
To run Envoy, you have to install a Java Runtime Environment (JRE) of at least version 11.
|
||||
@ -29,7 +31,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.
|
||||
|
||||
Because the project lacks external documentation for the moment, please refer to the Javadoc inside the source code to configure your Envoy instance.
|
||||
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.
|
||||
|
||||
### System requirements
|
||||
|
||||
|
@ -21,7 +21,6 @@
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
<attribute name="module" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
|
@ -22,20 +22,21 @@ import envoy.client.net.WriteProxy;
|
||||
*/
|
||||
public class Chat implements Serializable {
|
||||
|
||||
protected boolean disabled;
|
||||
protected transient ObservableList<Message> messages = FXCollections.observableArrayList();
|
||||
|
||||
protected final Contact recipient;
|
||||
|
||||
protected boolean disabled;
|
||||
protected boolean underlyingContactDeleted;
|
||||
|
||||
/**
|
||||
* Stores the last time an {@link envoy.event.IsTyping} event has been sent.
|
||||
*/
|
||||
protected transient long lastWritingEvent;
|
||||
|
||||
protected transient ObservableList<Message> messages = FXCollections.observableArrayList();
|
||||
|
||||
protected int unreadAmount;
|
||||
protected static IntegerProperty totalUnreadAmount = new SimpleIntegerProperty();
|
||||
|
||||
protected final Contact recipient;
|
||||
|
||||
private static final long serialVersionUID = 2L;
|
||||
|
||||
/**
|
||||
|
@ -13,9 +13,8 @@ import java.util.stream.Stream;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.*;
|
||||
|
||||
import dev.kske.eventbus.Event;
|
||||
import dev.kske.eventbus.EventBus;
|
||||
import dev.kske.eventbus.EventListener;
|
||||
import dev.kske.eventbus.core.*;
|
||||
import dev.kske.eventbus.core.Event;
|
||||
|
||||
import envoy.data.*;
|
||||
import envoy.data.Message.MessageStatus;
|
||||
@ -35,7 +34,7 @@ import envoy.client.event.*;
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Client v0.3-alpha
|
||||
*/
|
||||
public final class LocalDB implements EventListener {
|
||||
public final class LocalDB {
|
||||
|
||||
// Data
|
||||
private User user;
|
||||
@ -246,8 +245,13 @@ public final class LocalDB implements EventListener {
|
||||
* @throws IOException if the saving process failed
|
||||
* @since Envoy Client v0.3-alpha
|
||||
*/
|
||||
@Event(eventType = EnvoyCloseEvent.class, priority = 500)
|
||||
@Event(EnvoyCloseEvent.class)
|
||||
@Priority(500)
|
||||
private synchronized void save() {
|
||||
|
||||
// Stop saving if this account has been deleted
|
||||
if (userFile == null)
|
||||
return;
|
||||
EnvoyLog.getLogger(LocalDB.class).log(Level.FINER, "Saving local database...");
|
||||
|
||||
// Save users
|
||||
@ -273,37 +277,66 @@ public final class LocalDB implements EventListener {
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
if (msg.getStatus() == MessageStatus.SENT)
|
||||
msg.nextStatus();
|
||||
}
|
||||
|
||||
@Event(priority = 500)
|
||||
@Event
|
||||
@Priority(500)
|
||||
private void onGroupMessage(GroupMessage msg) {
|
||||
// TODO: Cancel event once EventBus is updated
|
||||
if (msg.getStatus() == MessageStatus.WAITING || msg.getStatus() == MessageStatus.READ)
|
||||
logger.warning("The groupMessage has the unexpected status " + msg.getStatus());
|
||||
}
|
||||
|
||||
@Event(priority = 500)
|
||||
@Event
|
||||
@Priority(500)
|
||||
private void onMessageStatusChange(MessageStatusChange evt) {
|
||||
getMessage(evt.getID()).ifPresent(msg -> msg.setStatus(evt.get()));
|
||||
}
|
||||
|
||||
@Event(priority = 500)
|
||||
@Event
|
||||
@Priority(500)
|
||||
private void onGroupMessageStatusChange(GroupMessageStatusChange evt) {
|
||||
this.<GroupMessage>getMessage(evt.getID())
|
||||
.ifPresent(msg -> msg.getMemberStatuses().replace(evt.getMemberID(), evt.get()));
|
||||
}
|
||||
|
||||
@Event(priority = 500)
|
||||
@Event
|
||||
@Priority(500)
|
||||
private void onUserStatusChange(UserStatusChange evt) {
|
||||
getChat(evt.getID()).map(Chat::getRecipient).map(User.class::cast)
|
||||
.ifPresent(u -> u.setStatus(evt.get()));
|
||||
}
|
||||
|
||||
@Event(priority = 500)
|
||||
@Event
|
||||
@Priority(500)
|
||||
private void onUserOperation(UserOperation operation) {
|
||||
final var eventUser = operation.get();
|
||||
switch (operation.getOperationType()) {
|
||||
@ -329,13 +362,15 @@ public final class LocalDB implements EventListener {
|
||||
Platform.runLater(() -> chats.add(new GroupChat(user, newGroup)));
|
||||
}
|
||||
|
||||
@Event(priority = 500)
|
||||
@Event
|
||||
@Priority(500)
|
||||
private void onGroupResize(GroupResize evt) {
|
||||
getChat(evt.getGroupID()).map(Chat::getRecipient).map(Group.class::cast)
|
||||
.ifPresent(evt::apply);
|
||||
}
|
||||
|
||||
@Event(priority = 500)
|
||||
@Event
|
||||
@Priority(500)
|
||||
private void onNameChange(NameChange evt) {
|
||||
chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == evt.getID()).findAny()
|
||||
.ifPresent(c -> c.setName(evt.get()));
|
||||
@ -357,7 +392,8 @@ public final class LocalDB implements EventListener {
|
||||
*
|
||||
* @since Envoy Client v0.2-beta
|
||||
*/
|
||||
@Event(eventType = Logout.class, priority = 50)
|
||||
@Event(Logout.class)
|
||||
@Priority(50)
|
||||
private void onLogout() {
|
||||
autoSaver.cancel();
|
||||
autoSaveRestart = true;
|
||||
@ -389,21 +425,33 @@ public final class LocalDB implements EventListener {
|
||||
});
|
||||
}
|
||||
|
||||
@Event(priority = 500)
|
||||
@Event
|
||||
@Priority(500)
|
||||
private void onOwnStatusChange(OwnStatusChange statusChange) {
|
||||
user.setStatus(statusChange.get());
|
||||
}
|
||||
|
||||
@Event(eventType = ContactsChangedSinceLastLogin.class, priority = 500)
|
||||
@Event(ContactsChangedSinceLastLogin.class)
|
||||
@Priority(500)
|
||||
private void onContactsChangedSinceLastLogin() {
|
||||
contactsChanged = true;
|
||||
}
|
||||
|
||||
@Event(priority = 500)
|
||||
@Event
|
||||
@Priority(500)
|
||||
private void onContactDisabled(ContactDisabled event) {
|
||||
getChat(event.get().getID()).ifPresent(chat -> chat.setDisabled(true));
|
||||
}
|
||||
|
||||
@Event
|
||||
@Priority(500)
|
||||
private void onAccountDeletion(AccountDeletion deletion) {
|
||||
if (user.getID() == deletion.get())
|
||||
logger.log(Level.WARNING,
|
||||
"I have been informed by the server that I have been deleted without even knowing it...");
|
||||
getChat(deletion.get()).ifPresent(chat -> chat.setDisabled(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a {@code Map<String, User>} of all users stored locally with their user names as keys
|
||||
* @since Envoy Client v0.2-alpha
|
||||
@ -461,7 +509,8 @@ public final class LocalDB implements EventListener {
|
||||
* @param idGenerator the message ID generator to set
|
||||
* @since Envoy Client v0.3-alpha
|
||||
*/
|
||||
@Event(priority = 150)
|
||||
@Event
|
||||
@Priority(150)
|
||||
public void setIDGenerator(IDGenerator idGenerator) { this.idGenerator = idGenerator; }
|
||||
|
||||
/**
|
||||
|
@ -5,8 +5,7 @@ import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.prefs.Preferences;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
import dev.kske.eventbus.EventListener;
|
||||
import dev.kske.eventbus.core.*;
|
||||
|
||||
import envoy.util.*;
|
||||
|
||||
@ -21,7 +20,7 @@ import envoy.client.event.EnvoyCloseEvent;
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Client v0.2-alpha
|
||||
*/
|
||||
public final class Settings implements EventListener {
|
||||
public final class Settings {
|
||||
|
||||
// Actual settings accessible by the rest of the application
|
||||
private Map<String, SettingsItem<?>> items;
|
||||
@ -69,7 +68,7 @@ public final class Settings implements EventListener {
|
||||
* @throws IOException if an error occurs while saving the themes
|
||||
* @since Envoy Client v0.2-alpha
|
||||
*/
|
||||
@Event(eventType = EnvoyCloseEvent.class)
|
||||
@Event(EnvoyCloseEvent.class)
|
||||
private void save() {
|
||||
EnvoyLog.getLogger(Settings.class).log(Level.INFO, "Saving settings...");
|
||||
|
||||
|
@ -6,7 +6,7 @@ import envoy.data.User.UserStatus;
|
||||
|
||||
import envoy.client.data.Context;
|
||||
import envoy.client.helper.ShutdownHelper;
|
||||
import envoy.client.ui.SceneContext.SceneInfo;
|
||||
import envoy.client.ui.SceneInfo;
|
||||
import envoy.client.util.UserUtil;
|
||||
|
||||
/**
|
||||
|
@ -4,7 +4,7 @@ import java.util.*;
|
||||
|
||||
import javafx.scene.input.KeyCombination;
|
||||
|
||||
import envoy.client.ui.SceneContext.SceneInfo;
|
||||
import envoy.client.ui.SceneInfo;
|
||||
|
||||
/**
|
||||
* Contains all keyboard shortcuts used throughout the application.
|
||||
|
22
client/src/main/java/envoy/client/event/AccountDeletion.java
Normal file
22
client/src/main/java/envoy/client/event/AccountDeletion.java
Normal file
@ -0,0 +1,22 @@
|
||||
package envoy.client.event;
|
||||
|
||||
import envoy.event.Event;
|
||||
|
||||
/**
|
||||
* Signifies the deletion of an account.
|
||||
*
|
||||
* @author Leon Hofmeister
|
||||
* @since Envoy Common v0.3-beta
|
||||
*/
|
||||
public class AccountDeletion extends Event<Long> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* @param value the ID of the contact that was deleted
|
||||
* @since Envoy Common v0.3-beta
|
||||
*/
|
||||
public AccountDeletion(Long value) {
|
||||
super(value);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package envoy.client.helper;
|
||||
|
||||
import dev.kske.eventbus.EventBus;
|
||||
import dev.kske.eventbus.core.EventBus;
|
||||
|
||||
import envoy.client.data.*;
|
||||
import envoy.client.event.EnvoyCloseEvent;
|
||||
|
@ -5,14 +5,14 @@ import java.net.Socket;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.*;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
import dev.kske.eventbus.Event;
|
||||
import dev.kske.eventbus.core.*;
|
||||
import dev.kske.eventbus.core.Event;
|
||||
|
||||
import envoy.data.*;
|
||||
import envoy.event.*;
|
||||
import envoy.util.*;
|
||||
|
||||
import envoy.client.data.*;
|
||||
import envoy.client.data.ClientConfig;
|
||||
import envoy.client.event.EnvoyCloseEvent;
|
||||
|
||||
/**
|
||||
@ -24,7 +24,7 @@ import envoy.client.event.EnvoyCloseEvent;
|
||||
* @author Leon Hofmeister
|
||||
* @since Envoy Client v0.1-alpha
|
||||
*/
|
||||
public final class Client implements EventListener, Closeable {
|
||||
public final class Client implements Closeable {
|
||||
|
||||
// Connection handling
|
||||
private Socket socket;
|
||||
@ -55,13 +55,12 @@ public final class Client implements EventListener, Closeable {
|
||||
* the handshake does exceed this time limit, an exception is thrown.
|
||||
*
|
||||
* @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 IOException if the login credentials could not be written
|
||||
* @throws InterruptedException if the current thread is interrupted while waiting for the
|
||||
* handshake response
|
||||
*/
|
||||
public void performHandshake(LoginCredentials credentials, CacheMap cacheMap)
|
||||
public void performHandshake(LoginCredentials credentials)
|
||||
throws TimeoutException, IOException, InterruptedException {
|
||||
if (online)
|
||||
throw new IllegalStateException("Handshake has already been performed successfully");
|
||||
@ -79,7 +78,6 @@ public final class Client implements EventListener, Closeable {
|
||||
// Register user creation processor, contact list processor, message cache and
|
||||
// authentication token
|
||||
receiver.registerProcessor(User.class, sender -> this.sender = sender);
|
||||
receiver.registerProcessors(cacheMap.getMap());
|
||||
|
||||
// Start receiver
|
||||
receiver.start();
|
||||
@ -101,42 +99,18 @@ public final class Client implements EventListener, Closeable {
|
||||
|
||||
if (System.currentTimeMillis() - start > 5000) {
|
||||
rejected = true;
|
||||
socket.close();
|
||||
receiver.removeAllProcessors();
|
||||
throw new TimeoutException("Did not log in after 5 seconds");
|
||||
}
|
||||
Thread.sleep(500);
|
||||
}
|
||||
|
||||
online = true;
|
||||
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
|
||||
// Remove handshake specific processors
|
||||
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);
|
||||
online = true;
|
||||
logger.log(Level.INFO, "Handshake completed.");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,13 +153,15 @@ public final class Client implements EventListener, Closeable {
|
||||
send(new IDGeneratorRequest());
|
||||
}
|
||||
|
||||
@Event(eventType = HandshakeRejection.class, priority = 1000)
|
||||
@Event(HandshakeRejection.class)
|
||||
@Priority(1000)
|
||||
private void onHandshakeRejection() {
|
||||
rejected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Event(eventType = EnvoyCloseEvent.class, priority = 50)
|
||||
@Event(EnvoyCloseEvent.class)
|
||||
@Priority(50)
|
||||
public void close() {
|
||||
if (online) {
|
||||
logger.log(Level.INFO, "Closing connection...");
|
||||
|
@ -6,7 +6,7 @@ import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.*;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
import dev.kske.eventbus.core.EventBus;
|
||||
|
||||
import envoy.util.*;
|
||||
|
||||
@ -87,15 +87,17 @@ public final class Receiver extends Thread {
|
||||
// Dispatch to the processor if present
|
||||
if (processor != null)
|
||||
processor.accept(obj);
|
||||
// Dispatch to the event bus if the object is an event without a processor
|
||||
else if (obj instanceof IEvent)
|
||||
eventBus.dispatch((IEvent) obj);
|
||||
// Notify if no processor could be located
|
||||
// Dispatch to the event bus if the object has no processor
|
||||
else
|
||||
logger.log(Level.WARNING,
|
||||
String.format(
|
||||
"The received object has the %s for which no processor is defined.",
|
||||
obj.getClass()));
|
||||
eventBus.dispatch(obj);
|
||||
|
||||
// TODO: Log DeadEvent from Event Bus 1.1.0
|
||||
// Notify if no processor could be located
|
||||
// else
|
||||
// logger.log(Level.WARNING,
|
||||
// String.format(
|
||||
// "The received object has the %s for which no processor is defined.",
|
||||
// obj.getClass()));
|
||||
}
|
||||
} catch (final SocketException | EOFException e) {
|
||||
// Connection probably closed by client.
|
||||
|
@ -4,12 +4,11 @@ import java.io.IOException;
|
||||
import java.util.Stack;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.*;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
import dev.kske.eventbus.core.*;
|
||||
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
@ -26,53 +25,13 @@ import envoy.client.event.*;
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Client v0.1-beta
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
public final class SceneContext {
|
||||
|
||||
private final Stage stage;
|
||||
private final FXMLLoader loader = new FXMLLoader();
|
||||
private final Stack<Scene> sceneStack = new Stack<>();
|
||||
private final Stack<Object> controllerStack = new Stack<>();
|
||||
private final Stack<Parent> roots = new Stack<>();
|
||||
private final Stack<Object> controllers = new Stack<>();
|
||||
|
||||
private static final Settings settings = Settings.getInstance();
|
||||
private Scene scene;
|
||||
|
||||
/**
|
||||
* Initializes the scene context.
|
||||
@ -88,44 +47,44 @@ public final class SceneContext implements EventListener {
|
||||
/**
|
||||
* Loads a new scene specified by a scene info.
|
||||
*
|
||||
* @param sceneInfo specifies the scene to load
|
||||
* @param info specifies the scene to load
|
||||
* @throws RuntimeException if the loading process fails
|
||||
* @since Envoy Client v0.1-beta
|
||||
*/
|
||||
public void load(SceneInfo sceneInfo) {
|
||||
EnvoyLog.getLogger(SceneContext.class).log(Level.FINER, "Loading scene " + sceneInfo);
|
||||
loader.setRoot(null);
|
||||
loader.setController(null);
|
||||
public void load(SceneInfo info) {
|
||||
EnvoyLog.getLogger(SceneContext.class).log(Level.FINER, "Loading scene " + info);
|
||||
|
||||
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);
|
||||
|
||||
sceneStack.push(scene);
|
||||
stage.setScene(scene);
|
||||
// Load root node and controller
|
||||
var loader = new FXMLLoader();
|
||||
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
|
||||
scene.getAccelerators()
|
||||
.putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(sceneInfo));
|
||||
.putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(info));
|
||||
|
||||
// Supply the scene specific keyboard shortcuts
|
||||
if (controller instanceof KeyboardMapping)
|
||||
scene.getAccelerators()
|
||||
.putAll(((KeyboardMapping) controller).getKeyboardShortcuts());
|
||||
|
||||
// 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);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
@ -137,42 +96,45 @@ public final class SceneContext implements EventListener {
|
||||
*/
|
||||
public void pop() {
|
||||
|
||||
// Pop scene and controller
|
||||
sceneStack.pop();
|
||||
controllerStack.pop();
|
||||
// Pop current root node and controller
|
||||
roots.pop();
|
||||
controllers.pop();
|
||||
|
||||
// Apply new scene if present
|
||||
if (!sceneStack.isEmpty()) {
|
||||
final var newScene = sceneStack.peek();
|
||||
stage.setScene(newScene);
|
||||
applyCSS();
|
||||
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 (!roots.isEmpty()) {
|
||||
scene.setRoot(roots.peek());
|
||||
|
||||
// Invoke restore if controller is restorable
|
||||
var controller = controllers.peek();
|
||||
if (controller instanceof Restorable)
|
||||
((Restorable) controller).onRestore();
|
||||
} else {
|
||||
|
||||
// Remove the current scene entirely
|
||||
scene = null;
|
||||
stage.setScene(null);
|
||||
}
|
||||
stage.show();
|
||||
}
|
||||
|
||||
private void applyCSS() {
|
||||
if (!sceneStack.isEmpty()) {
|
||||
final var styleSheets = stage.getScene().getStylesheets();
|
||||
final var themeCSS = "/css/" + settings.getCurrentTheme() + ".css";
|
||||
if (scene != null) {
|
||||
var styleSheets = scene.getStylesheets();
|
||||
var themeCSS = "/css/" + Settings.getInstance().getCurrentTheme() + ".css";
|
||||
styleSheets.clear();
|
||||
styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(),
|
||||
getClass().getResource(themeCSS).toExternalForm());
|
||||
}
|
||||
}
|
||||
|
||||
@Event(eventType = Logout.class, priority = 150)
|
||||
@Event(Logout.class)
|
||||
@Priority(150)
|
||||
private void onLogout() {
|
||||
sceneStack.clear();
|
||||
controllerStack.clear();
|
||||
roots.clear();
|
||||
controllers.clear();
|
||||
}
|
||||
|
||||
@Event(priority = 150, eventType = ThemeChangeEvent.class)
|
||||
@Event(ThemeChangeEvent.class)
|
||||
@Priority(150)
|
||||
private void onThemeChange() {
|
||||
applyCSS();
|
||||
}
|
||||
@ -182,7 +144,7 @@ public final class SceneContext implements EventListener {
|
||||
* @return the controller used by the current scene
|
||||
* @since Envoy Client v0.1-beta
|
||||
*/
|
||||
public <T> T getController() { return (T) controllerStack.peek(); }
|
||||
public <T> T getController() { return (T) controllers.peek(); }
|
||||
|
||||
/**
|
||||
* @return the stage in which the scenes are displayed
|
||||
@ -194,5 +156,5 @@ public final class SceneContext implements EventListener {
|
||||
* @return whether the scene stack is empty
|
||||
* @since Envoy Client v0.2-beta
|
||||
*/
|
||||
public boolean isEmpty() { return sceneStack.isEmpty(); }
|
||||
public boolean isEmpty() { return roots.isEmpty(); }
|
||||
}
|
||||
|
40
client/src/main/java/envoy/client/ui/SceneInfo.java
Normal file
40
client/src/main/java/envoy/client/ui/SceneInfo.java
Normal file
@ -0,0 +1,40 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ import javafx.stage.Stage;
|
||||
|
||||
import envoy.data.*;
|
||||
import envoy.data.User.UserStatus;
|
||||
import envoy.event.*;
|
||||
import envoy.event.UserStatusChange;
|
||||
import envoy.exception.EnvoyException;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
@ -20,7 +20,6 @@ import envoy.client.data.*;
|
||||
import envoy.client.data.shortcuts.EnvoyShortcutConfig;
|
||||
import envoy.client.helper.ShutdownHelper;
|
||||
import envoy.client.net.Client;
|
||||
import envoy.client.ui.SceneContext.SceneInfo;
|
||||
import envoy.client.ui.controller.LoginScene;
|
||||
import envoy.client.util.IconUtil;
|
||||
|
||||
@ -94,7 +93,7 @@ public final class Startup extends Application {
|
||||
final var sceneContext = new SceneContext(stage);
|
||||
context.setSceneContext(sceneContext);
|
||||
|
||||
// Authenticate with token if present
|
||||
// Authenticate with token if present or load login scene
|
||||
if (localDB.getAuthToken() != null) {
|
||||
logger.info("Attempting authentication with token...");
|
||||
localDB.loadUserData();
|
||||
@ -103,8 +102,9 @@ public final class Startup extends Application {
|
||||
VERSION, localDB.getLastSync())))
|
||||
sceneContext.load(SceneInfo.LOGIN_SCENE);
|
||||
} else
|
||||
// Load login scene
|
||||
sceneContext.load(SceneInfo.LOGIN_SCENE);
|
||||
|
||||
stage.show();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,21 +115,20 @@ public final class Startup extends Application {
|
||||
* @since Envoy Client v0.2-beta
|
||||
*/
|
||||
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 =
|
||||
localDB.getUser() == null ? UserStatus.ONLINE : localDB.getUser().getStatus();
|
||||
try {
|
||||
client.performHandshake(credentials, cacheMap);
|
||||
client.performHandshake(credentials);
|
||||
if (client.isOnline()) {
|
||||
|
||||
// Restore the original status as the server automatically returns status ONLINE
|
||||
client.getSender().setStatus(originalStatus);
|
||||
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;
|
||||
} else
|
||||
return false;
|
||||
@ -226,7 +225,7 @@ public final class Startup extends Application {
|
||||
// Load ChatScene
|
||||
stage.setMinHeight(400);
|
||||
stage.setMinWidth(843);
|
||||
context.getSceneContext().load(SceneContext.SceneInfo.CHAT_SCENE);
|
||||
context.getSceneContext().load(SceneInfo.CHAT_SCENE);
|
||||
stage.centerOnScreen();
|
||||
|
||||
// Exit or minimize the stage when a close request occurs
|
||||
|
@ -9,8 +9,8 @@ import java.awt.image.BufferedImage;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
import dev.kske.eventbus.Event;
|
||||
import dev.kske.eventbus.core.Event;
|
||||
import dev.kske.eventbus.core.EventBus;
|
||||
|
||||
import envoy.data.Message;
|
||||
import envoy.data.User.UserStatus;
|
||||
@ -31,7 +31,7 @@ import envoy.client.util.*;
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Client v0.2-alpha
|
||||
*/
|
||||
public final class StatusTrayIcon implements EventListener {
|
||||
public final class StatusTrayIcon {
|
||||
|
||||
/**
|
||||
* The {@link TrayIcon} provided by the System Tray API for controlling the system tray. This
|
||||
@ -136,7 +136,7 @@ public final class StatusTrayIcon implements EventListener {
|
||||
*
|
||||
* @since Envoy Client v0.2-beta
|
||||
*/
|
||||
@Event(eventType = Logout.class)
|
||||
@Event(Logout.class)
|
||||
public void hide() {
|
||||
SystemTray.getSystemTray().remove(trayIcon);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import envoy.util.EnvoyLog;
|
||||
import envoy.client.data.Context;
|
||||
import envoy.client.data.commands.*;
|
||||
import envoy.client.helper.ShutdownHelper;
|
||||
import envoy.client.ui.SceneContext.SceneInfo;
|
||||
import envoy.client.ui.SceneInfo;
|
||||
import envoy.client.ui.controller.ChatScene;
|
||||
import envoy.client.util.*;
|
||||
|
||||
@ -32,7 +32,7 @@ public final class ChatSceneCommands {
|
||||
private final SystemCommandBuilder builder =
|
||||
new SystemCommandBuilder(messageTextAreaCommands);
|
||||
|
||||
private static final String messageDependantCommandDescription =
|
||||
private static final String messageDependentCommandDescription =
|
||||
" 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
|
||||
useRelativeMessage(command, action, additionalCheck, positionalArgument, false);
|
||||
}).setDefaults("s").setNumberOfArguments(1)
|
||||
.setDescription(description.concat(messageDependantCommandDescription)).build(command);
|
||||
.setDescription(description.concat(messageDependentCommandDescription)).build(command);
|
||||
}
|
||||
|
||||
private void selectionNeighbor(Consumer<Message> action, Predicate<Message> additionalCheck,
|
||||
|
@ -18,7 +18,7 @@ import envoy.client.util.IconUtil;
|
||||
*/
|
||||
public final class ChatControl extends HBox {
|
||||
|
||||
private static final Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32),
|
||||
private static Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32),
|
||||
groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
|
||||
|
||||
/**
|
||||
@ -60,4 +60,14 @@ public final class ChatControl extends HBox {
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
package envoy.client.ui.controller;
|
||||
|
||||
import static envoy.client.ui.SceneInfo.SETTINGS_SCENE;
|
||||
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import java.util.logging.*;
|
||||
|
||||
import javafx.animation.RotateTransition;
|
||||
@ -18,14 +21,14 @@ import javafx.scene.control.*;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.image.*;
|
||||
import javafx.scene.input.*;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
import dev.kske.eventbus.Event;
|
||||
import dev.kske.eventbus.core.*;
|
||||
import dev.kske.eventbus.core.Event;
|
||||
|
||||
import envoy.data.*;
|
||||
import envoy.data.Attachment.AttachmentType;
|
||||
@ -37,6 +40,7 @@ import envoy.util.EnvoyLog;
|
||||
|
||||
import envoy.client.data.*;
|
||||
import envoy.client.data.audio.AudioRecorder;
|
||||
import envoy.client.data.shortcuts.KeyboardMapping;
|
||||
import envoy.client.event.*;
|
||||
import envoy.client.net.*;
|
||||
import envoy.client.ui.*;
|
||||
@ -51,7 +55,7 @@ import envoy.client.util.*;
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Client v0.1-beta
|
||||
*/
|
||||
public final class ChatScene implements EventListener, Restorable {
|
||||
public final class ChatScene implements Restorable, KeyboardMapping {
|
||||
|
||||
@FXML
|
||||
private ListView<Message> messageList;
|
||||
@ -216,12 +220,13 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
});
|
||||
}
|
||||
|
||||
@Event(eventType = BackEvent.class)
|
||||
@Event(BackEvent.class)
|
||||
private void onBackEvent() {
|
||||
tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal());
|
||||
}
|
||||
|
||||
@Event(includeSubtypes = true)
|
||||
@Event
|
||||
@Polymorphic
|
||||
private void onMessage(Message message) {
|
||||
|
||||
// The sender of the message is the recipient of the chat
|
||||
@ -300,7 +305,7 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
}));
|
||||
}
|
||||
|
||||
@Event(eventType = NoAttachments.class)
|
||||
@Event(NoAttachments.class)
|
||||
private void onNoAttachments() {
|
||||
Platform.runLater(() -> {
|
||||
attachmentButton.setDisable(true);
|
||||
@ -313,13 +318,15 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
});
|
||||
}
|
||||
|
||||
@Event(priority = 150)
|
||||
@Event
|
||||
@Priority(150)
|
||||
private void onGroupCreationResult(GroupCreationResult result) {
|
||||
Platform.runLater(() -> newGroupButton.setDisable(result.get() == null));
|
||||
}
|
||||
|
||||
@Event(eventType = ThemeChangeEvent.class)
|
||||
@Event(ThemeChangeEvent.class)
|
||||
private void onThemeChange() {
|
||||
ChatControl.reloadDefaultChatIcons();
|
||||
settingsButton.setGraphic(
|
||||
new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
|
||||
voiceButton.setGraphic(
|
||||
@ -341,11 +348,17 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
|
||||
}
|
||||
|
||||
@Event(eventType = Logout.class, priority = 200)
|
||||
@Event(Logout.class)
|
||||
@Priority(200)
|
||||
private void onLogout() {
|
||||
eventBus.removeListener(this);
|
||||
}
|
||||
|
||||
@Event(AccountDeletion.class)
|
||||
private void onAccountDeletion() {
|
||||
Platform.runLater(chatList::refresh);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRestore() {
|
||||
updateRemainingCharsLabel();
|
||||
@ -438,7 +451,7 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
*/
|
||||
@FXML
|
||||
private void settingsButtonClicked() {
|
||||
sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE);
|
||||
sceneContext.load(SETTINGS_SCENE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -586,7 +599,7 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
// IsTyping#millisecondsActive
|
||||
if (client.isOnline() && currentChat.getLastWritingEvent()
|
||||
+ IsTyping.millisecondsActive <= System.currentTimeMillis()) {
|
||||
client.send(new IsTyping(getChatID(), currentChat.getRecipient().getID()));
|
||||
client.send(new IsTyping(currentChat.getRecipient().getID()));
|
||||
currentChat.lastWritingEventWasNow();
|
||||
}
|
||||
|
||||
@ -600,19 +613,6 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
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
|
||||
* @since Envoy Client v0.1-beta
|
||||
@ -786,7 +786,8 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
attachmentView.setVisible(visible);
|
||||
}
|
||||
|
||||
@Event(eventType = OwnStatusChange.class, priority = 50)
|
||||
@Event(OwnStatusChange.class)
|
||||
@Priority(50)
|
||||
private void generateOwnStatusControl() {
|
||||
|
||||
// Update the own user status if present
|
||||
@ -797,7 +798,7 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
// Else prepend it to the HBox children
|
||||
final var ownUserControl = new ContactControl(localDB.getUser());
|
||||
ownUserControl.setAlignment(Pos.CENTER_LEFT);
|
||||
HBox.setHgrow(ownUserControl, Priority.NEVER);
|
||||
HBox.setHgrow(ownUserControl, javafx.scene.layout.Priority.NEVER);
|
||||
ownContactControl.getChildren().add(1, ownUserControl);
|
||||
}
|
||||
}
|
||||
@ -884,4 +885,25 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
: c -> c.getRecipient().getName().toLowerCase()
|
||||
.contains(contactSearch.getText().toLowerCase()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<KeyCombination, Runnable> getKeyboardShortcuts() {
|
||||
return Map.<KeyCombination, Runnable>of(
|
||||
|
||||
// Delete text before the caret with "Control" + U
|
||||
new KeyCodeCombination(KeyCode.U, KeyCombination.CONTROL_DOWN), () -> {
|
||||
messageTextArea
|
||||
.setText(
|
||||
messageTextArea.getText().substring(messageTextArea.getCaretPosition()));
|
||||
checkPostConditions(false);
|
||||
|
||||
// Delete text after the caret with "Control" + K
|
||||
}, new KeyCodeCombination(KeyCode.K, KeyCombination.CONTROL_DOWN), () -> {
|
||||
messageTextArea
|
||||
.setText(
|
||||
messageTextArea.getText().substring(0, messageTextArea.getCaretPosition()));
|
||||
checkPostConditions(false);
|
||||
messageTextArea.positionCaret(messageTextArea.getText().length());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
import dev.kske.eventbus.core.*;
|
||||
|
||||
import envoy.data.User;
|
||||
import envoy.event.ElementOperation;
|
||||
@ -34,7 +34,7 @@ import envoy.client.ui.listcell.ListCellFactory;
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Client v0.1-beta
|
||||
*/
|
||||
public class ContactSearchTab implements EventListener {
|
||||
public class ContactSearchTab {
|
||||
|
||||
@FXML
|
||||
private TextArea searchBar;
|
||||
|
@ -10,7 +10,7 @@ import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
import dev.kske.eventbus.core.*;
|
||||
|
||||
import envoy.data.*;
|
||||
import envoy.event.GroupCreation;
|
||||
@ -18,7 +18,7 @@ import envoy.event.contact.UserOperation;
|
||||
import envoy.util.Bounds;
|
||||
|
||||
import envoy.client.data.*;
|
||||
import envoy.client.event.BackEvent;
|
||||
import envoy.client.event.*;
|
||||
import envoy.client.ui.control.*;
|
||||
import envoy.client.ui.listcell.ListCellFactory;
|
||||
|
||||
@ -33,7 +33,7 @@ import envoy.client.ui.listcell.ListCellFactory;
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Client v0.1-beta
|
||||
*/
|
||||
public class GroupCreationTab implements EventListener {
|
||||
public class GroupCreationTab {
|
||||
|
||||
@FXML
|
||||
private Button createButton;
|
||||
@ -252,4 +252,10 @@ public class GroupCreationTab implements EventListener {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Event
|
||||
private void onAccountDeletion(AccountDeletion deletion) {
|
||||
final var deletedID = deletion.get();
|
||||
Platform.runLater(() -> userList.getItems().removeIf(user -> (user.getID() == deletedID)));
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import javafx.scene.control.*;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.image.ImageView;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
import dev.kske.eventbus.core.*;
|
||||
|
||||
import envoy.data.LoginCredentials;
|
||||
import envoy.event.HandshakeRejection;
|
||||
@ -27,7 +27,7 @@ import envoy.client.util.IconUtil;
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Client v0.1-beta
|
||||
*/
|
||||
public final class LoginScene implements EventListener {
|
||||
public final class LoginScene {
|
||||
|
||||
@FXML
|
||||
private TextField userTextField;
|
||||
|
@ -2,7 +2,7 @@ package envoy.client.ui.settings;
|
||||
|
||||
import javafx.scene.control.*;
|
||||
|
||||
import dev.kske.eventbus.EventBus;
|
||||
import dev.kske.eventbus.core.EventBus;
|
||||
|
||||
import envoy.data.User.UserStatus;
|
||||
|
||||
|
@ -13,14 +13,15 @@ import javafx.scene.image.*;
|
||||
import javafx.scene.input.InputEvent;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import dev.kske.eventbus.EventBus;
|
||||
import dev.kske.eventbus.core.EventBus;
|
||||
|
||||
import envoy.event.*;
|
||||
import envoy.util.*;
|
||||
|
||||
import envoy.client.ui.control.ProfilePicImageView;
|
||||
import envoy.client.util.IconUtil;
|
||||
import envoy.client.util.*;
|
||||
|
||||
/**
|
||||
* @author Leon Hofmeister
|
||||
@ -38,6 +39,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
||||
private final PasswordField newPasswordField = new PasswordField();
|
||||
private final PasswordField repeatNewPasswordField = new PasswordField();
|
||||
private final Button saveButton = new Button("Save");
|
||||
private final Button deleteAccountButton = new Button("Delete Account (Locally)");
|
||||
|
||||
private static final EventBus eventBus = EventBus.getInstance();
|
||||
private static final Logger logger = EnvoyLog.getLogger(UserSettingsPane.class);
|
||||
@ -112,16 +114,19 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
||||
|
||||
final PasswordField[] passwordFields =
|
||||
{ currentPasswordField, newPasswordField, repeatNewPasswordField };
|
||||
final EventHandler<? super InputEvent> passwordEntered = e -> {
|
||||
newPassword =
|
||||
newPasswordField.getText();
|
||||
validPassword = newPassword
|
||||
.equals(
|
||||
repeatNewPasswordField
|
||||
.getText())
|
||||
&& !newPasswordField
|
||||
.getText().isBlank();
|
||||
};
|
||||
final EventHandler<? super InputEvent> passwordEntered =
|
||||
e -> {
|
||||
newPassword =
|
||||
newPasswordField
|
||||
.getText();
|
||||
validPassword =
|
||||
newPassword.equals(
|
||||
repeatNewPasswordField
|
||||
.getText())
|
||||
&& !newPasswordField
|
||||
.getText()
|
||||
.isBlank();
|
||||
};
|
||||
newPasswordField.setOnInputMethodTextChanged(passwordEntered);
|
||||
newPasswordField.setOnKeyTyped(passwordEntered);
|
||||
repeatNewPasswordField.setOnInputMethodTextChanged(passwordEntered);
|
||||
@ -137,9 +142,21 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
||||
|
||||
// Displaying the save button
|
||||
saveButton
|
||||
.setOnAction(e -> save(client.getSender().getID(), currentPasswordField.getText()));
|
||||
.setOnAction(e -> save(currentPasswordField.getText()));
|
||||
saveButton.setAlignment(Pos.BOTTOM_RIGHT);
|
||||
getChildren().add(saveButton);
|
||||
|
||||
// Displaying the delete account button
|
||||
deleteAccountButton.setAlignment(Pos.BASELINE_CENTER);
|
||||
deleteAccountButton.setOnAction(e -> UserUtil.deleteAccount());
|
||||
deleteAccountButton.setText("Delete Account (locally)");
|
||||
deleteAccountButton.setPrefHeight(25);
|
||||
deleteAccountButton.getStyleClass().clear();
|
||||
deleteAccountButton.getStyleClass().add("danger-button");
|
||||
final var tooltip = new Tooltip("Remote deletion is currently unsupported.");
|
||||
tooltip.setShowDelay(Duration.millis(100));
|
||||
deleteAccountButton.setTooltip(tooltip);
|
||||
getChildren().add(deleteAccountButton);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -148,11 +165,11 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
||||
* @param username the new username
|
||||
* @since Envoy Client v0.2-beta
|
||||
*/
|
||||
private void save(long userID, String oldPassword) {
|
||||
private void save(String oldPassword) {
|
||||
|
||||
// The profile pic was changed
|
||||
if (profilePicChanged) {
|
||||
final var profilePicChangeEvent = new ProfilePicChange(currentImageBytes, userID);
|
||||
final var profilePicChangeEvent = new ProfilePicChange(currentImageBytes);
|
||||
eventBus.dispatch(profilePicChangeEvent);
|
||||
client.send(profilePicChangeEvent);
|
||||
logger.log(Level.INFO, "The user just changed his profile pic.");
|
||||
@ -161,7 +178,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
||||
// The username was changed
|
||||
final var validContactName = Bounds.isValidContactName(newUsername);
|
||||
if (usernameChanged && validContactName) {
|
||||
final var nameChangeEvent = new NameChange(userID, newUsername);
|
||||
final var nameChangeEvent = new NameChange(client.getSender().getID(), newUsername);
|
||||
eventBus.dispatch(nameChangeEvent);
|
||||
client.send(nameChangeEvent);
|
||||
logger.log(Level.INFO, "The user just changed his name to " + newUsername + ".");
|
||||
@ -178,7 +195,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
|
||||
|
||||
// The password was changed
|
||||
if (validPassword) {
|
||||
client.send(new PasswordChangeRequest(newPassword, oldPassword, userID));
|
||||
client.send(new PasswordChangeRequest(newPassword, oldPassword));
|
||||
logger.log(Level.INFO, "The user just tried to change his password!");
|
||||
} else if (!(validPassword || newPassword.isBlank())) {
|
||||
final var alert = new Alert(AlertType.ERROR);
|
||||
|
@ -7,7 +7,7 @@ import java.util.logging.*;
|
||||
|
||||
import javafx.stage.FileChooser;
|
||||
|
||||
import dev.kske.eventbus.EventBus;
|
||||
import dev.kske.eventbus.core.EventBus;
|
||||
|
||||
import envoy.data.Message;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
@ -2,10 +2,10 @@ package envoy.client.util;
|
||||
|
||||
import java.util.logging.*;
|
||||
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
|
||||
import dev.kske.eventbus.EventBus;
|
||||
import dev.kske.eventbus.core.EventBus;
|
||||
|
||||
import envoy.data.*;
|
||||
import envoy.data.User.UserStatus;
|
||||
@ -16,7 +16,7 @@ import envoy.util.EnvoyLog;
|
||||
import envoy.client.data.Context;
|
||||
import envoy.client.event.*;
|
||||
import envoy.client.helper.*;
|
||||
import envoy.client.ui.SceneContext.SceneInfo;
|
||||
import envoy.client.ui.SceneInfo;
|
||||
import envoy.client.ui.controller.ChatScene;
|
||||
|
||||
/**
|
||||
@ -121,4 +121,35 @@ public final class UserUtil {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes anything pointing to this user, independent of client or server. Will do nothing if
|
||||
* the client is currently offline.
|
||||
*
|
||||
* @since Envoy Client v0.3-beta
|
||||
*/
|
||||
public static void deleteAccount() {
|
||||
|
||||
// Show the first wall of defense, if not disabled by the user
|
||||
final var outerAlert = new Alert(AlertType.CONFIRMATION);
|
||||
outerAlert.setContentText(
|
||||
"Are you sure you want to delete your account entirely? This action can seriously not be undone.");
|
||||
outerAlert.setTitle("Delete Account?");
|
||||
AlertHelper.confirmAction(outerAlert, () -> {
|
||||
|
||||
// Show the final wall of defense in every case
|
||||
final var lastAlert = new Alert(AlertType.WARNING,
|
||||
"Do you REALLY want to delete your account? Last Warning. Proceed?",
|
||||
ButtonType.CANCEL, ButtonType.OK);
|
||||
lastAlert.setTitle("Delete Account?");
|
||||
lastAlert.showAndWait().filter(ButtonType.OK::equals).ifPresent(b2 -> {
|
||||
|
||||
// Delete the account
|
||||
// TODO: Notify server of account deletion
|
||||
context.getLocalDB().delete();
|
||||
logger.log(Level.INFO, "The user just deleted his account. Goodbye.");
|
||||
ShutdownHelper.exit(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,13 @@ module envoy.client {
|
||||
requires javafx.fxml;
|
||||
requires javafx.base;
|
||||
requires javafx.graphics;
|
||||
requires 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;
|
||||
opens envoy.client.ui.chatscene to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus;
|
||||
opens envoy.client.ui to javafx.graphics, javafx.fxml, dev.kske.eventbus.core;
|
||||
opens envoy.client.ui.controller 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.core;
|
||||
opens envoy.client.ui.control to javafx.graphics, javafx.fxml;
|
||||
opens envoy.client.ui.settings to envoy.client.util;
|
||||
opens envoy.client.net to dev.kske.eventbus;
|
||||
opens envoy.client.data to dev.kske.eventbus;
|
||||
opens envoy.client.net to dev.kske.eventbus.core;
|
||||
opens envoy.client.data to dev.kske.eventbus.core;
|
||||
}
|
||||
|
@ -70,6 +70,17 @@
|
||||
-fx-text-fill: gray;
|
||||
}
|
||||
|
||||
.danger-button {
|
||||
-fx-background-color: red;
|
||||
-fx-text-fill: white;
|
||||
-fx-background-radius: 0.2em;
|
||||
}
|
||||
|
||||
.danger-button:hover {
|
||||
-fx-scale-x: 1.05;
|
||||
-fx-scale-y: 1.05;
|
||||
}
|
||||
|
||||
.received-message {
|
||||
-fx-alignment: center-left;
|
||||
-fx-background-radius: 1.3em;
|
||||
|
@ -30,6 +30,10 @@
|
||||
-fx-background-color: black;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
-fx-text-fill: black;
|
||||
}
|
||||
|
||||
#login-input-field {
|
||||
-fx-border-color: black;
|
||||
}
|
||||
|
@ -24,8 +24,8 @@
|
||||
|
||||
<GridPane maxHeight="-Infinity" maxWidth="-Infinity"
|
||||
minHeight="400.0" minWidth="500.0"
|
||||
prefHeight="${screen.visualBounds.height}"
|
||||
prefWidth="${screen.visualBounds.width}"
|
||||
prefHeight="${screen.visualBounds.height}"
|
||||
xmlns="http://javafx.com/javafx/11.0.1"
|
||||
xmlns:fx="http://javafx.com/fxml/1"
|
||||
fx:controller="envoy.client.ui.controller.ChatScene">
|
||||
@ -57,8 +57,7 @@
|
||||
<content>
|
||||
<AnchorPane minHeight="0.0" minWidth="0.0">
|
||||
<children>
|
||||
<VBox prefHeight="3000.0"
|
||||
prefWidth="316.0">
|
||||
<VBox prefHeight="3000.0" prefWidth="316.0">
|
||||
<children>
|
||||
<VBox id="search-panel" maxHeight="-Infinity"
|
||||
minHeight="-Infinity" prefHeight="80.0" prefWidth="316.0">
|
||||
@ -156,8 +155,7 @@
|
||||
<Insets left="15.0" top="5.0" right="10.0" />
|
||||
</HBox.margin>
|
||||
</ImageView>
|
||||
<Region id="transparent-background"
|
||||
HBox.hgrow="ALWAYS" />
|
||||
<Region id="transparent-background" HBox.hgrow="ALWAYS" />
|
||||
<Button fx:id="settingsButton" mnemonicParsing="false"
|
||||
onAction="#settingsButtonClicked" prefHeight="30.0"
|
||||
prefWidth="30.0" alignment="CENTER_RIGHT">
|
||||
@ -165,7 +163,7 @@
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</padding>
|
||||
<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>
|
||||
</Button>
|
||||
</children>
|
||||
|
@ -7,11 +7,16 @@
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<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">
|
||||
<VBox alignment="TOP_RIGHT" maxHeight="-Infinity" minHeight="400.0"
|
||||
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>
|
||||
<HBox prefHeight="389.0" prefWidth="600.0">
|
||||
<children>
|
||||
<ListView id="message-list" fx:id="settingsList" onMouseClicked="#settingsListClicked" prefHeight="200.0" prefWidth="200.0">
|
||||
<ListView id="message-list" fx:id="settingsList"
|
||||
onMouseClicked="#settingsListClicked" prefHeight="200.0"
|
||||
prefWidth="200.0">
|
||||
<opaqueInsets>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</opaqueInsets>
|
||||
@ -22,7 +27,8 @@
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
</padding>
|
||||
</ListView>
|
||||
<TitledPane fx:id="titledPane" collapsible="false" prefHeight="400.0" prefWidth="400.0">
|
||||
<TitledPane fx:id="titledPane" collapsible="false"
|
||||
prefHeight="400.0" prefWidth="400.0">
|
||||
<HBox.margin>
|
||||
<Insets bottom="10.0" left="5.0" right="10.0" top="10.0" />
|
||||
</HBox.margin>
|
||||
@ -32,7 +38,8 @@
|
||||
</TitledPane>
|
||||
</children>
|
||||
</HBox>
|
||||
<Button defaultButton="true" mnemonicParsing="true" onMouseClicked="#backButtonClicked" text="_Back">
|
||||
<Button defaultButton="true" mnemonicParsing="true"
|
||||
onMouseClicked="#backButtonClicked" text="_Back">
|
||||
<opaqueInsets>
|
||||
<Insets />
|
||||
</opaqueInsets>
|
||||
|
@ -12,18 +12,11 @@
|
||||
<version>0.2-beta</version>
|
||||
</parent>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>kske-repo</id>
|
||||
<url>https://kske.dev/maven-repo</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>dev.kske</groupId>
|
||||
<artifactId>event-bus</artifactId>
|
||||
<version>0.0.4</version>
|
||||
<artifactId>event-bus-core</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package envoy.data;
|
||||
|
||||
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.
|
||||
@ -63,9 +64,9 @@ public final class Attachment implements Serializable {
|
||||
* @since Envoy Common v0.1-beta
|
||||
*/
|
||||
public Attachment(byte[] data, String name, AttachmentType type) {
|
||||
this.data = data;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.data = Objects.requireNonNull(data);
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.type = Objects.requireNonNull(type);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,8 +29,8 @@ public abstract class Contact implements Serializable {
|
||||
*/
|
||||
public Contact(long id, String name, Set<? extends Contact> contacts) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.contacts = contacts;
|
||||
this.name = Objects.requireNonNull(name);
|
||||
this.contacts = contacts == null ? new HashSet<>() : contacts;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,7 +38,8 @@ public final class GroupMessage extends Message {
|
||||
Map<Long, MessageStatus> memberStatuses) {
|
||||
super(id, senderID, groupID, creationDate, receivedDate, readDate, text, attachment, status,
|
||||
forwarded);
|
||||
this.memberStatuses = memberStatuses;
|
||||
this.memberStatuses =
|
||||
memberStatuses == null ? new HashMap<>() : memberStatuses;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,15 +2,13 @@ package envoy.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import dev.kske.eventbus.IEvent;
|
||||
|
||||
/**
|
||||
* Generates increasing IDs between two numbers.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Common v0.2-alpha
|
||||
*/
|
||||
public final class IDGenerator implements IEvent, Serializable {
|
||||
public final class IDGenerator implements Serializable {
|
||||
|
||||
private final long end;
|
||||
private long current;
|
||||
|
@ -2,6 +2,7 @@ package envoy.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Contains a {@link User}'s login / registration information as well as the client version.
|
||||
@ -20,15 +21,14 @@ public final class LoginCredentials implements Serializable {
|
||||
private static final long serialVersionUID = 4;
|
||||
|
||||
private LoginCredentials(String identifier, String password, boolean registration,
|
||||
boolean token, boolean requestToken, String clientVersion,
|
||||
Instant lastSync) {
|
||||
this.identifier = identifier;
|
||||
this.password = password;
|
||||
boolean token, boolean requestToken, String clientVersion, Instant lastSync) {
|
||||
this.identifier = Objects.requireNonNull(identifier);
|
||||
this.password = Objects.requireNonNull(password);
|
||||
this.registration = registration;
|
||||
this.token = token;
|
||||
this.requestToken = requestToken;
|
||||
this.clientVersion = clientVersion;
|
||||
this.lastSync = lastSync;
|
||||
this.clientVersion = Objects.requireNonNull(clientVersion);
|
||||
this.lastSync = lastSync == null ? Instant.EPOCH : lastSync;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,7 +75,8 @@ public final class LoginCredentials implements Serializable {
|
||||
* @since Envoy Common v0.2-beta
|
||||
*/
|
||||
public static LoginCredentials registration(String identifier, String password,
|
||||
boolean requestToken, String clientVersion, Instant lastSync) {
|
||||
boolean requestToken,
|
||||
String clientVersion, Instant lastSync) {
|
||||
return new LoginCredentials(identifier, password, true, false, requestToken, clientVersion,
|
||||
lastSync);
|
||||
}
|
||||
|
@ -2,8 +2,7 @@ package envoy.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
|
||||
import dev.kske.eventbus.IEvent;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a unique message with a unique, numeric ID. Further metadata includes the sender and
|
||||
@ -13,7 +12,7 @@ import dev.kske.eventbus.IEvent;
|
||||
* @author Leon Hofmeister
|
||||
* @since Envoy Common v0.2-alpha
|
||||
*/
|
||||
public class Message implements Serializable, IEvent {
|
||||
public class Message implements Serializable {
|
||||
|
||||
/**
|
||||
* This enumeration defines all possible statuses a {link Message} can have.
|
||||
@ -80,9 +79,9 @@ public class Message implements Serializable, IEvent {
|
||||
this.creationDate = creationDate;
|
||||
this.receivedDate = receivedDate;
|
||||
this.readDate = readDate;
|
||||
this.text = text;
|
||||
this.text = text == null ? "" : text;
|
||||
this.attachment = attachment;
|
||||
this.status = status;
|
||||
this.status = Objects.requireNonNull(status);
|
||||
this.forwarded = forwarded;
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ public final class User extends Contact {
|
||||
*/
|
||||
public User(long id, String name, UserStatus status, Set<Contact> contacts) {
|
||||
super(id, name, contacts);
|
||||
this.status = status;
|
||||
this.status = Objects.requireNonNull(status);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,26 +1,33 @@
|
||||
package envoy.event;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import dev.kske.eventbus.IEvent;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This class serves as a convenience base class for all events. It implements the {@link IEvent}
|
||||
* interface and provides a generic value. For events without a value there also is
|
||||
* {@link envoy.event.Event.Valueless}.
|
||||
* This class serves as a convenience base class for all events. It provides a generic value. For
|
||||
* events without a value there also is {@link envoy.event.Event.Valueless}.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @param <T> the type of the Event
|
||||
* @since Envoy v0.2-alpha
|
||||
*/
|
||||
public abstract class Event<T> implements IEvent, Serializable {
|
||||
public abstract class Event<T> implements Serializable {
|
||||
|
||||
protected final T value;
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
protected Event(T value) {
|
||||
this.value = value;
|
||||
this(value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,7 +53,7 @@ public abstract class Event<T> implements IEvent, Serializable {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
protected Valueless() {
|
||||
super(null);
|
||||
super(null, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,7 +20,7 @@ public class GroupCreationResult extends Event<Group> {
|
||||
* @since Envoy Common v0.2-beta
|
||||
*/
|
||||
public GroupCreationResult() {
|
||||
super(null);
|
||||
super(null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -30,7 +30,7 @@ public final class GroupMessageStatusChange extends MessageStatusChange {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the memberID which the user who sends this event has
|
||||
* @return the ID of the sender of this event
|
||||
* @since Envoy Common v0.1-beta
|
||||
*/
|
||||
public long getMemberID() { return memberID; }
|
||||
|
@ -2,6 +2,8 @@ package envoy.event;
|
||||
|
||||
import static envoy.event.ElementOperation.*;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import envoy.data.*;
|
||||
|
||||
/**
|
||||
@ -30,7 +32,7 @@ public final class GroupResize extends Event<User> {
|
||||
*/
|
||||
public GroupResize(User user, Group group, ElementOperation operation) {
|
||||
super(user);
|
||||
this.operation = operation;
|
||||
this.operation = Objects.requireNonNull(operation);
|
||||
final var contained = group.getContacts().contains(user);
|
||||
if (contained && operation.equals(ADD))
|
||||
throw new IllegalArgumentException(String.format("Cannot add %s to %s!", user, group));
|
||||
|
@ -8,8 +8,6 @@ package envoy.event;
|
||||
*/
|
||||
public final class IsTyping extends Event<Long> {
|
||||
|
||||
private final long destinationID;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
@ -22,20 +20,13 @@ public final class IsTyping extends Event<Long> {
|
||||
public static final int millisecondsActive = 3500;
|
||||
|
||||
/**
|
||||
* Creates a new {@code IsTyping} event with originator and recipient.
|
||||
* Creates a new {@code IsTyping}. The client will only send the contact that should receive
|
||||
* this event. The server will send the id of the contact who sent this event.
|
||||
*
|
||||
* @param sourceID the ID of the originator
|
||||
* @param destinationID the ID of the contact the user wrote to
|
||||
* @param id the ID of the recipient (client)/ originator(server)
|
||||
* @since Envoy Common v0.2-beta
|
||||
*/
|
||||
public IsTyping(Long sourceID, long destinationID) {
|
||||
super(sourceID);
|
||||
this.destinationID = destinationID;
|
||||
public IsTyping(long id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ID of the contact in whose chat the user typed something
|
||||
* @since Envoy Common v0.2-beta
|
||||
*/
|
||||
public long getDestinationID() { return destinationID; }
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ public final class IssueProposal extends Event<String> {
|
||||
*/
|
||||
public IssueProposal(String title, String description, boolean isBug) {
|
||||
super(escape(title));
|
||||
this.description = sanitizeDescription(description);
|
||||
this.description = description == null ? "" : sanitizeDescription(description);
|
||||
bug = isBug;
|
||||
}
|
||||
|
||||
@ -37,8 +37,8 @@ public final class IssueProposal extends Event<String> {
|
||||
*/
|
||||
public IssueProposal(String title, String description, String user, boolean isBug) {
|
||||
super(escape(title));
|
||||
this.description =
|
||||
sanitizeDescription(description) + String.format("<br>Submitted by user %s.", user);
|
||||
this.description = description == null ? ""
|
||||
: sanitizeDescription(description) + String.format("<br>Submitted by user %s.", user);
|
||||
bug = isBug;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package envoy.event;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
import envoy.data.Message;
|
||||
|
||||
@ -26,7 +27,7 @@ public class MessageStatusChange extends Event<Message.MessageStatus> {
|
||||
public MessageStatusChange(long id, Message.MessageStatus status, Instant date) {
|
||||
super(status);
|
||||
this.id = id;
|
||||
this.date = date;
|
||||
this.date = Objects.requireNonNull(date);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
package envoy.event;
|
||||
|
||||
import envoy.data.Contact;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Leon Hofmeister
|
||||
@ -8,29 +8,20 @@ import envoy.data.Contact;
|
||||
*/
|
||||
public final class PasswordChangeRequest extends Event<String> {
|
||||
|
||||
private final long id;
|
||||
private final String oldPassword;
|
||||
private final String oldPassword;
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
/**
|
||||
* @param newPassword the new 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
|
||||
*/
|
||||
public PasswordChangeRequest(String newPassword, String oldPassword, long userID) {
|
||||
public PasswordChangeRequest(String newPassword, String oldPassword) {
|
||||
super(newPassword);
|
||||
this.oldPassword = oldPassword;
|
||||
id = userID;
|
||||
this.oldPassword = Objects.requireNonNull(oldPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @since Envoy Common v0.2-beta
|
||||
@ -39,6 +30,6 @@ public final class PasswordChangeRequest extends Event<String> {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PasswordChangeRequest[id=" + id + "]";
|
||||
return "PasswordChangeRequest[]";
|
||||
}
|
||||
}
|
||||
|
@ -6,23 +6,13 @@ package envoy.event;
|
||||
*/
|
||||
public final class ProfilePicChange extends Event<byte[]> {
|
||||
|
||||
private final long id;
|
||||
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
/**
|
||||
* @param value the byte[] of the new image
|
||||
* @param userID the ID of the user who changed his profile pic
|
||||
* @param value the byte[] of the new image
|
||||
* @since Envoy Common v0.2-beta
|
||||
*/
|
||||
public ProfilePicChange(byte[] value, long userID) {
|
||||
public ProfilePicChange(byte[] 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; }
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package envoy.event.contact;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import envoy.data.User;
|
||||
import envoy.event.*;
|
||||
|
||||
@ -24,7 +26,7 @@ public final class UserOperation extends Event<User> {
|
||||
*/
|
||||
public UserOperation(User contact, ElementOperation operationType) {
|
||||
super(contact);
|
||||
this.operationType = operationType;
|
||||
this.operationType = Objects.requireNonNull(operationType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,5 +16,5 @@ module envoy.common {
|
||||
exports envoy.event.contact;
|
||||
|
||||
requires transitive java.logging;
|
||||
requires transitive dev.kske.eventbus;
|
||||
requires transitive dev.kske.eventbus.core;
|
||||
}
|
||||
|
@ -18,10 +18,10 @@ import envoy.util.SerializationUtils;
|
||||
* @author Leon Hofmeister
|
||||
* @since Envoy Common v0.1-beta
|
||||
*/
|
||||
class UserTest {
|
||||
public class UserTest {
|
||||
|
||||
@Test
|
||||
void test() throws IOException, ClassNotFoundException {
|
||||
public void test() throws IOException, ClassNotFoundException {
|
||||
User user2 = new User(2, "kai");
|
||||
User user3 = new User(3, "ai");
|
||||
User user4 = new User(4, "ki", Set.of(user2, user3));
|
||||
|
7
pom.xml
7
pom.xml
@ -28,6 +28,13 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.0.0-M5</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<modules>
|
||||
|
@ -1,7 +1,7 @@
|
||||
package envoy.server.data;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.persistence.*;
|
||||
@ -121,8 +121,9 @@ public final class PersistenceManager {
|
||||
transaction(() -> {
|
||||
|
||||
// Remove this contact from the contact list of his contacts
|
||||
for (final var remainingContact : contact.getContacts())
|
||||
for (final var remainingContact : contact.contacts)
|
||||
remainingContact.getContacts().remove(contact);
|
||||
contact.contacts.clear();
|
||||
});
|
||||
remove(contact);
|
||||
}
|
||||
@ -223,6 +224,9 @@ public final class PersistenceManager {
|
||||
* @since Envoy Server Standalone v0.2-beta
|
||||
*/
|
||||
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)
|
||||
.setParameter("lastSeen", lastSync).getResultList();
|
||||
}
|
||||
@ -236,6 +240,9 @@ public final class PersistenceManager {
|
||||
* @since Envoy Server Standalone v0.2-beta
|
||||
*/
|
||||
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)
|
||||
.setParameter("userId", user.getID())
|
||||
.setParameter("lastSeen", lastSync)
|
||||
@ -277,16 +284,18 @@ public final class PersistenceManager {
|
||||
* @since Envoy Server v0.3-beta
|
||||
*/
|
||||
public void addContactBidirectional(Contact contact1, Contact contact2) {
|
||||
if (!(contact1 == null || contact2 == null)) {
|
||||
|
||||
// Add users to each others contact list
|
||||
contact1.getContacts().add(contact2);
|
||||
contact2.getContacts().add(contact1);
|
||||
// Add users to each others contact list
|
||||
contact1.getContacts().add(contact2);
|
||||
contact2.getContacts().add(contact1);
|
||||
|
||||
// Synchronize changes with the database
|
||||
transaction(() -> {
|
||||
entityManager.merge(contact1);
|
||||
entityManager.merge(contact2);
|
||||
});
|
||||
// Synchronize changes with the database
|
||||
transaction(() -> {
|
||||
entityManager.merge(contact1);
|
||||
entityManager.merge(contact2);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -308,16 +317,18 @@ public final class PersistenceManager {
|
||||
* @since Envoy Server v0.3-beta
|
||||
*/
|
||||
public void removeContactBidirectional(Contact contact1, Contact contact2) {
|
||||
if (!(contact1 == null || contact2 == null)) {
|
||||
|
||||
// Remove users from each others contact list
|
||||
contact1.getContacts().remove(contact2);
|
||||
contact2.getContacts().remove(contact1);
|
||||
// Remove users from each others contact list
|
||||
contact1.getContacts().remove(contact2);
|
||||
contact2.getContacts().remove(contact1);
|
||||
|
||||
// Synchronize changes with the database
|
||||
transaction(() -> {
|
||||
entityManager.merge(contact1);
|
||||
entityManager.merge(contact2);
|
||||
});
|
||||
// Synchronize changes with the database
|
||||
transaction(() -> {
|
||||
entityManager.merge(contact1);
|
||||
entityManager.merge(contact2);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -331,15 +342,36 @@ public final class PersistenceManager {
|
||||
}
|
||||
|
||||
private void persist(Object obj) {
|
||||
transaction(() -> entityManager.persist(obj));
|
||||
try {
|
||||
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) {
|
||||
transaction(() -> entityManager.merge(obj));
|
||||
try {
|
||||
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) {
|
||||
transaction(() -> entityManager.remove(obj));
|
||||
try {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,8 +49,10 @@ public final class ConnectionManager implements ISocketIdListener {
|
||||
// Notify contacts of this users offline-going
|
||||
final envoy.server.data.User user =
|
||||
PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID));
|
||||
user.setLastSeen(Instant.now());
|
||||
UserStatusChangeProcessor.updateUserStatus(user, UserStatus.OFFLINE);
|
||||
if (user != null) {
|
||||
user.setLastSeen(Instant.now());
|
||||
UserStatusChangeProcessor.updateUserStatus(user, UserStatus.OFFLINE);
|
||||
}
|
||||
|
||||
// Remove the socket
|
||||
sockets.entrySet().removeIf(e -> e.getValue() == socketID);
|
||||
|
@ -33,7 +33,6 @@ public final class ObjectMessageProcessor implements IMessageProcessor {
|
||||
this.processors = processors;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void process(Message message, WriteProxy writeProxy) {
|
||||
try (ObjectInputStream in =
|
||||
@ -45,23 +44,34 @@ public final class ObjectMessageProcessor implements IMessageProcessor {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.fine("Received " + obj);
|
||||
logger.log(Level.INFO, "Received " + 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);
|
||||
}
|
||||
}
|
||||
refer(message.socketId, writeProxy, obj);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
logger.log(Level.WARNING,
|
||||
"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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import static envoy.server.Startup.config;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.logging.*;
|
||||
|
||||
import javax.persistence.EntityExistsException;
|
||||
|
||||
@ -15,6 +15,7 @@ import envoy.util.EnvoyLog;
|
||||
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.*;
|
||||
import envoy.server.util.UserAuthenticationUtil;
|
||||
|
||||
/**
|
||||
* @author Maximilian Käfer
|
||||
@ -29,6 +30,15 @@ public final class GroupMessageProcessor implements ObjectProcessor<GroupMessage
|
||||
|
||||
@Override
|
||||
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();
|
||||
|
||||
// Update statuses to SENT / RECEIVED depending on online status
|
||||
|
@ -12,6 +12,7 @@ import envoy.util.EnvoyLog;
|
||||
|
||||
import envoy.server.data.*;
|
||||
import envoy.server.net.*;
|
||||
import envoy.server.util.UserAuthenticationUtil;
|
||||
|
||||
/**
|
||||
* @author Maximilian Käfer
|
||||
@ -28,7 +29,17 @@ public final class GroupMessageStatusChangeProcessor
|
||||
@Override
|
||||
public void process(GroupMessageStatusChange statusChange, long socketID,
|
||||
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());
|
||||
if (gmsg == null)
|
||||
return;
|
||||
|
||||
// Any other status than READ is not supposed to be sent to the server
|
||||
if (statusChange.get() != MessageStatus.READ) {
|
||||
|
@ -24,6 +24,10 @@ public final class GroupResizeProcessor implements ObjectProcessor<GroupResize>
|
||||
final var group = persistenceManager.getGroupByID(groupResize.getGroupID());
|
||||
final var sender = persistenceManager.getUserByID(groupResize.get().getID());
|
||||
|
||||
// TODO: Inform the sender that this group has already been deleted
|
||||
if (group == null)
|
||||
return;
|
||||
|
||||
// Perform the desired operation
|
||||
switch (groupResize.getOperation()) {
|
||||
case ADD:
|
||||
|
@ -23,10 +23,11 @@ public final class IsTypingProcessor implements ObjectProcessor<IsTyping> {
|
||||
throws IOException {
|
||||
final var contact = persistenceManager.getContactByID(event.get());
|
||||
if (contact instanceof User) {
|
||||
final var destinationID = event.getDestinationID();
|
||||
if (connectionManager.isOnline(destinationID))
|
||||
writeProxy.write(connectionManager.getSocketID(destinationID), event);
|
||||
if (connectionManager.isOnline(event.get()))
|
||||
writeProxy.write(connectionManager.getSocketID(event.get()),
|
||||
new IsTyping(connectionManager.getUserIDBySocketID(socketID)));
|
||||
} else
|
||||
writeProxy.writeToOnlineContacts(contact.getContacts(), event);
|
||||
writeProxy.writeToOnlineContacts(contact.getContacts(),
|
||||
new IsTyping(connectionManager.getUserIDBySocketID(socketID)));
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
// Cache this write proxy for user-independent notifications
|
||||
UserStatusChangeProcessor.setWriteProxy(writeProxy);
|
||||
|
||||
// Check for compatible versions
|
||||
if (!VersionUtil.verifyCompatibility(credentials.getClientVersion())) {
|
||||
logger.info("The client has the wrong version.");
|
||||
writeProxy.write(socketID, new HandshakeRejection(WRONG_VERSION));
|
||||
@ -70,10 +71,10 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN));
|
||||
return;
|
||||
}
|
||||
} else
|
||||
} else if (!PasswordUtil.validate(credentials.getPassword(),
|
||||
user.getPasswordHash())) {
|
||||
|
||||
// Check the password hash
|
||||
if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
|
||||
// Check the password hash
|
||||
logger.info(user + " has entered the wrong password.");
|
||||
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
|
||||
return;
|
||||
@ -101,7 +102,8 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
writeProxy.write(socketID, new HandshakeRejection(USERNAME_TAKEN));
|
||||
return;
|
||||
} catch (final NoResultException e) {
|
||||
// Creation of a new user
|
||||
|
||||
// Create a new user
|
||||
user = new User();
|
||||
user.setName(credentials.getIdentifier());
|
||||
user.setLastSeen(Instant.now());
|
||||
@ -123,7 +125,6 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
// Process token request
|
||||
if (credentials.requestToken()) {
|
||||
String token;
|
||||
|
||||
if (user.getAuthToken() != null && user.getAuthTokenExpiration().isAfter(Instant.now()))
|
||||
|
||||
// Reuse existing token and delay expiration date
|
||||
@ -140,6 +141,14 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
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 =
|
||||
PersistenceManager.getInstance().getPendingMessages(user, credentials.getLastSync());
|
||||
pendingMessages.removeIf(GroupMessage.class::isInstance);
|
||||
@ -164,8 +173,9 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
writeProxy.write(connectionManager.getSocketID(msg.getSender().getID()),
|
||||
new MessageStatusChange(msgCommon));
|
||||
}
|
||||
} else
|
||||
} else {
|
||||
writeProxy.write(socketID, new MessageStatusChange(msgCommon));
|
||||
}
|
||||
}
|
||||
|
||||
final List<GroupMessage> pendingGroupMessages = PersistenceManager.getInstance()
|
||||
@ -199,10 +209,11 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
}
|
||||
|
||||
PersistenceManager.getInstance().updateMessage(gmsg);
|
||||
} else
|
||||
} else {
|
||||
|
||||
// Just send the message without updating if it was received in the past
|
||||
writeProxy.write(socketID, gmsgCommon);
|
||||
}
|
||||
} else {
|
||||
|
||||
// Sending group message status changes
|
||||
@ -222,11 +233,5 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import envoy.util.EnvoyLog;
|
||||
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.*;
|
||||
import envoy.server.util.UserAuthenticationUtil;
|
||||
|
||||
/**
|
||||
* This {@link ObjectProcessor} handles incoming {@link Message}s.
|
||||
@ -29,6 +30,15 @@ public final class MessageProcessor implements ObjectProcessor<Message> {
|
||||
|
||||
@Override
|
||||
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();
|
||||
|
||||
// Convert to server message
|
||||
|
@ -32,6 +32,8 @@ public final class MessageStatusChangeProcessor implements ObjectProcessor<Messa
|
||||
}
|
||||
|
||||
final var msg = persistenceManager.getMessageByID(statusChange.getID());
|
||||
if (msg == null)
|
||||
return;
|
||||
msg.read();
|
||||
persistenceManager.updateMessage(msg);
|
||||
|
||||
|
@ -7,7 +7,7 @@ import envoy.event.*;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
import envoy.server.data.PersistenceManager;
|
||||
import envoy.server.net.ObjectWriteProxy;
|
||||
import envoy.server.net.*;
|
||||
import envoy.server.util.PasswordUtil;
|
||||
|
||||
/**
|
||||
@ -21,7 +21,8 @@ public final class PasswordChangeRequestProcessor
|
||||
public void process(PasswordChangeRequest event, long socketID, ObjectWriteProxy writeProxy)
|
||||
throws IOException {
|
||||
final var persistenceManager = PersistenceManager.getInstance();
|
||||
final var user = persistenceManager.getUserByID(event.getID());
|
||||
final var user = persistenceManager
|
||||
.getUserByID(ConnectionManager.getInstance().getUserIDBySocketID(socketID));
|
||||
final var logger =
|
||||
EnvoyLog.getLogger(PasswordChangeRequestProcessor.class);
|
||||
final var correctAuthentication =
|
||||
|
@ -22,10 +22,16 @@ public final class UserOperationProcessor implements ObjectProcessor<UserOperati
|
||||
private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
|
||||
@Override
|
||||
public void process(UserOperation evt, long socketId, ObjectWriteProxy writeProxy) {
|
||||
final long userID = ConnectionManager.getInstance().getUserIDBySocketID(socketId);
|
||||
public void process(UserOperation evt, long socketID, ObjectWriteProxy writeProxy) {
|
||||
final long userID = ConnectionManager.getInstance().getUserIDBySocketID(socketID);
|
||||
final long contactID = evt.get().getID();
|
||||
final var sender = persistenceManager.getUserByID(userID);
|
||||
final var recipient = persistenceManager.getUserByID(contactID);
|
||||
|
||||
// TODO: Inform the sender if the requested contact has already been deleted
|
||||
if (recipient == null)
|
||||
return;
|
||||
|
||||
final var sender = persistenceManager.getUserByID(userID);
|
||||
switch (evt.getOperationType()) {
|
||||
case ADD:
|
||||
logger.log(Level.FINE,
|
||||
@ -45,7 +51,7 @@ public final class UserOperationProcessor implements ObjectProcessor<UserOperati
|
||||
sender.setLatestContactDeletion(Instant.now());
|
||||
|
||||
// Notify the removed contact on next startup(s) of this deletion
|
||||
persistenceManager.getUserByID(contactID).setLatestContactDeletion(Instant.now());
|
||||
recipient.setLatestContactDeletion(Instant.now());
|
||||
|
||||
// Notify the removed contact if online
|
||||
if (connectionManager.isOnline(contactID))
|
||||
|
@ -0,0 +1,24 @@
|
||||
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user