diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..8ff7fe0
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @CyB3RC0nN0R
diff --git a/README.md b/README.md
index 36901ac..48fa3a6 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Envoy Client
-
+
**Envoy Client** is one of two repositories needed to use the messenger Envoy.
The other one is **Envoy Common**.
diff --git a/src/main/java/envoy/client/data/Cache.java b/src/main/java/envoy/client/data/Cache.java
index 8060d2e..786e96b 100644
--- a/src/main/java/envoy/client/data/Cache.java
+++ b/src/main/java/envoy/client/data/Cache.java
@@ -4,13 +4,14 @@ import java.io.Serializable;
import java.util.LinkedList;
import java.util.Queue;
import java.util.function.Consumer;
+import java.util.logging.Level;
import java.util.logging.Logger;
import envoy.util.EnvoyLog;
/**
- * Stores elements in a queue to process them later.
- *
+ * Stores elements in a queue to process them later.
+ *
* Project: envoy-client
* File: Cache.java
* Created: 6 Feb 2020
@@ -25,7 +26,7 @@ public class Cache implements Consumer, Serializable {
private transient Consumer processor;
private static final Logger logger = EnvoyLog.getLogger(Cache.class);
- private static final long serialVersionUID = 0L;
+ private static final long serialVersionUID = 0L;
/**
* Adds an element to the cache.
@@ -35,10 +36,13 @@ public class Cache implements Consumer, Serializable {
*/
@Override
public void accept(T element) {
- logger.fine(String.format("Adding element %s to cache", element));
+ logger.log(Level.FINE, String.format("Adding element %s to cache", element));
elements.offer(element);
}
+ @Override
+ public String toString() { return String.format("Cache[elements=" + elements + "]"); }
+
/**
* Sets the processor to which cached elements are relayed.
*
diff --git a/src/main/java/envoy/client/data/Chat.java b/src/main/java/envoy/client/data/Chat.java
index a9148e2..7ca11ce 100644
--- a/src/main/java/envoy/client/data/Chat.java
+++ b/src/main/java/envoy/client/data/Chat.java
@@ -4,16 +4,17 @@ import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import envoy.client.net.WriteProxy;
import envoy.data.*;
import envoy.data.Message.MessageStatus;
-import envoy.event.MessageStatusChangeEvent;
+import envoy.event.MessageStatusChange;
/**
- * Represents a chat between two {@link User}s
+ * Represents a chat between two {@link User}s
* as a list of {@link Message} objects.
- *
+ *
* Project: envoy-client
* File: Chat.java
* Created: 19 Oct 2019
@@ -31,7 +32,7 @@ public final class Chat implements Serializable {
private static final long serialVersionUID = 1L;
/**
- * Provides the list of messages that the recipient receives.
+ * Provides the list of messages that the recipient receives.
* Saves the Messages in the corresponding chat at that Point.
*
* @param recipient the user who receives the messages
@@ -42,6 +43,27 @@ public final class Chat implements Serializable {
@Override
public String toString() { return String.format("Chat[recipient=%s,messages=%d]", recipient, messages.size()); }
+ /**
+ * Generates a hash code based on the recipient.
+ *
+ * @since Envoy Client v0.1-beta
+ */
+ @Override
+ public int hashCode() { return Objects.hash(recipient); }
+
+ /**
+ * Tests equality to another object based on the recipient.
+ *
+ * @since Envoy Client v0.1-beta
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof Chat)) return false;
+ Chat other = (Chat) obj;
+ return Objects.equals(recipient, other.recipient);
+ }
+
/**
* Sets the status of all chat messages received from the recipient to
* {@code READ} starting from the bottom and stopping once a read message is
@@ -49,7 +71,7 @@ public final class Chat implements Serializable {
*
* @param writeProxy the write proxy instance used to notify the server about
* the message status changes
- * @throws IOException if a {@link MessageStatusChangeEvent} could not be
+ * @throws IOException if a {@link MessageStatusChange} could not be
* delivered to the server
* @since Envoy Client v0.3-alpha
*/
@@ -59,7 +81,7 @@ public final class Chat implements Serializable {
if (m.getSenderID() == recipient.getID()) if (m.getStatus() == MessageStatus.READ) break;
else {
m.setStatus(MessageStatus.READ);
- writeProxy.writeMessageStatusChangeEvent(new MessageStatusChangeEvent(m));
+ writeProxy.writeMessageStatusChange(new MessageStatusChange(m));
}
}
}
diff --git a/src/main/java/envoy/client/data/ClientConfig.java b/src/main/java/envoy/client/data/ClientConfig.java
index 0b11e40..a30dffb 100644
--- a/src/main/java/envoy/client/data/ClientConfig.java
+++ b/src/main/java/envoy/client/data/ClientConfig.java
@@ -4,14 +4,15 @@ import java.io.File;
import java.util.function.Function;
import java.util.logging.Level;
+import envoy.client.ui.Startup;
import envoy.data.Config;
import envoy.data.ConfigItem;
import envoy.data.LoginCredentials;
/**
* Implements a configuration specific to the Envoy Client with default values
- * and convenience methods.
- *
+ * and convenience methods.
+ *
* Project: envoy-client
* File: ClientConfig.java
* Created: 01.03.2020
@@ -109,5 +110,5 @@ public class ClientConfig extends Config {
* the registration option
* @since Envoy Client v0.3-alpha
*/
- public LoginCredentials getLoginCredentials() { return new LoginCredentials(getUser(), getPassword(), false); }
+ public LoginCredentials getLoginCredentials() { return new LoginCredentials(getUser(), getPassword(), false, Startup.VERSION); }
}
diff --git a/src/main/java/envoy/client/data/LocalDB.java b/src/main/java/envoy/client/data/LocalDB.java
index 38aeea4..d8993f5 100644
--- a/src/main/java/envoy/client/data/LocalDB.java
+++ b/src/main/java/envoy/client/data/LocalDB.java
@@ -3,14 +3,14 @@ package envoy.client.data;
import java.util.*;
import envoy.data.*;
-import envoy.event.GroupResizeEvent;
-import envoy.event.MessageStatusChangeEvent;
-import envoy.event.NameChangeEvent;
+import envoy.event.GroupResize;
+import envoy.event.MessageStatusChange;
+import envoy.event.NameChange;
/**
* Stores information about the current {@link User} and their {@link Chat}s.
- * For message ID generation a {@link IDGenerator} is stored as well.
- *
+ * For message ID generation a {@link IDGenerator} is stored as well.
+ *
* Project: envoy-client
* File: LocalDB.java
* Created: 3 Feb 2020
@@ -20,12 +20,12 @@ import envoy.event.NameChangeEvent;
*/
public abstract class LocalDB {
- protected User user;
- protected Map users = new HashMap<>();
- protected List chats = new ArrayList<>();
- protected IDGenerator idGenerator;
- protected Cache messageCache = new Cache<>();
- protected Cache statusCache = new Cache<>();
+ protected User user;
+ protected Map users = new HashMap<>();
+ protected List chats = new ArrayList<>();
+ protected IDGenerator idGenerator;
+ protected Cache messageCache = new Cache<>();
+ protected Cache statusCache = new Cache<>();
/**
* Initializes a storage space for a user-specific list of chats.
@@ -66,6 +66,25 @@ public abstract class LocalDB {
*/
public void loadIDGenerator() {}
+ /**
+ * Synchronizes the contact list of the client user with the chat and user
+ * storage.
+ *
+ * @since Envoy Client v0.1-beta
+ */
+ public void synchronize() {
+ user.getContacts().stream().filter(u -> u instanceof User && !users.containsKey(u.getName())).forEach(u -> users.put(u.getName(), u));
+ users.put(user.getName(), user);
+
+ // Synchronize user status data
+ for (Contact contact : users.values())
+ if (contact instanceof User)
+ getChat(contact.getID()).ifPresent(chat -> { ((User) chat.getRecipient()).setStatus(((User) contact).getStatus()); });
+
+ // Create missing chats
+ user.getContacts().stream().filter(u -> !u.equals(user) && getChat(u.getID()).isEmpty()).map(Chat::new).forEach(chats::add);
+ }
+
/**
* @return a {@code Map} of all users stored locally with their
* user names as keys
@@ -73,11 +92,6 @@ public abstract class LocalDB {
*/
public Map getUsers() { return users; }
- /**
- * @param users the users to set
- */
- public void setUsers(Map users) { this.users = users; }
-
/**
* @return all saved {@link Chat} objects that list the client user as the
* sender
@@ -136,13 +150,13 @@ public abstract class LocalDB {
* @return the offline status cache
* @since Envoy Client v0.3-alpha
*/
- public Cache getStatusCache() { return statusCache; }
+ public Cache getStatusCache() { return statusCache; }
/**
* @param statusCache the offline status cache to set
* @since Envoy Client v0.3-alpha
*/
- public void setStatusCache(Cache statusCache) { this.statusCache = statusCache; }
+ public void setStatusCache(Cache statusCache) { this.statusCache = statusCache; }
/**
* Searches for a message by ID.
@@ -154,7 +168,7 @@ public abstract class LocalDB {
public Optional getMessage(long id) {
return chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny();
}
-
+
/**
* Searches for a chat by recipient ID.
*
@@ -162,27 +176,25 @@ public abstract class LocalDB {
* @return an optional containing the chat
* @since Envoy Client v0.1-beta
*/
- public Optional getChat(long recipientID) {
- return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny();
- }
+ public Optional getChat(long recipientID) { return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); }
/**
* Performs a contact name change if the corresponding contact is present.
*
- * @param event the {@link NameChangeEvent} to process
+ * @param event the {@link NameChange} to process
* @since Envoy Client v0.1-beta
*/
- public void replaceContactName(NameChangeEvent event) {
+ public void replaceContactName(NameChange event) {
chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == event.getID()).findAny().ifPresent(c -> c.setName(event.get()));
}
/**
* Performs a group resize operation if the corresponding group is present.
*
- * @param event the {@link GroupResizeEvent} to process
+ * @param event the {@link GroupResize} to process
* @since Envoy Client v0.1-beta
*/
- public void updateGroup(GroupResizeEvent event) {
+ public void updateGroup(GroupResize event) {
chats.stream()
.map(Chat::getRecipient)
.filter(Group.class::isInstance)
@@ -200,13 +212,4 @@ public abstract class LocalDB {
}
});
}
-
- /**
- * Creates a new {@link Chat} for all {@link Contact}s that do not have a chat.
- *
- * @since Envoy Client v0.1-beta
- */
- public void createMissingChats() {
- users.values().stream().filter(u -> !u.equals(user) && getChat(u.getID()).isEmpty()).map(Chat::new).forEach(chats::add);
- }
}
diff --git a/src/main/java/envoy/client/data/PersistentLocalDB.java b/src/main/java/envoy/client/data/PersistentLocalDB.java
index 9548516..cc6cad9 100644
--- a/src/main/java/envoy/client/data/PersistentLocalDB.java
+++ b/src/main/java/envoy/client/data/PersistentLocalDB.java
@@ -1,18 +1,18 @@
package envoy.client.data;
-import java.io.File;
-import java.io.IOException;
+import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
-import envoy.data.ConfigItem;
import envoy.data.IDGenerator;
+import envoy.data.Message;
+import envoy.event.MessageStatusChange;
import envoy.util.SerializationUtils;
/**
* Implements a {@link LocalDB} in a way that stores all information inside a
- * folder on the local file system.
- *
+ * folder on the local file system.
+ *
* Project: envoy-client
* File: PersistentLocalDB.java
* Created: 27.10.2019
@@ -21,92 +21,67 @@ import envoy.util.SerializationUtils;
* @author Maximilian Käfer
* @since Envoy Client v0.1-alpha
*/
-public class PersistentLocalDB extends LocalDB {
+public final class PersistentLocalDB extends LocalDB {
- private File localDBDir, localDBFile, usersFile, idGeneratorFile, messageCacheFile, statusCacheFile;
+ private File dbDir, userFile, idGeneratorFile, usersFile;
/**
- * Initializes an empty local database without a directory. All changes made to
- * this instance cannot be saved to the file system.
- *
- * This constructor shall be used in conjunction with the {@code ignoreLocalDB}
- * {@link ConfigItem}.
+ * Constructs an empty local database. To serialize any user-specific data to
+ * the file system, call {@link PersistentLocalDB#initializeUserStorage()} first
+ * and then {@link PersistentLocalDB#save()}.
*
- * @since Envoy Client v0.3-alpha
- */
- public PersistentLocalDB() {}
-
- /**
- * Constructs an empty local database. To serialize any chats to the file
- * system, call {@link PersistentLocalDB#initializeUserStorage()}.
- *
- * @param localDBDir the directory in which to store users and chats
- * @throws IOException if the PersistentLocalDB could not be initialized
+ * @param dbDir the directory in which to persist data
+ * @throws IOException if {@code dbDir} is a file (and not a directory)
* @since Envoy Client v0.1-alpha
*/
- public PersistentLocalDB(File localDBDir) throws IOException {
- this.localDBDir = localDBDir;
+ public PersistentLocalDB(File dbDir) throws IOException {
+ this.dbDir = dbDir;
- // Initialize local database directory
- if (localDBDir.exists() && !localDBDir.isDirectory())
- throw new IOException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath()));
- usersFile = new File(localDBDir, "users.db");
- idGeneratorFile = new File(localDBDir, "id_generator.db");
+ // Test if the database directory is actually a directory
+ if (dbDir.exists() && !dbDir.isDirectory())
+ throw new IOException(String.format("LocalDBDir '%s' is not a directory!", dbDir.getAbsolutePath()));
+
+ // Initialize global files
+ idGeneratorFile = new File(dbDir, "id_gen.db");
+ usersFile = new File(dbDir, "users.db");
}
/**
- * Creates a database file for a user-specific list of chats.
- * {@inheritDoc}
+ * Creates a database file for a user-specific list of chats.
*
- * @throws NullPointerException if the client user is not yet specified
+ * @throws IllegalStateException if the client user is not specified
* @since Envoy Client v0.1-alpha
*/
@Override
public void initializeUserStorage() {
- if (user == null) throw new NullPointerException("Client user is null");
- localDBFile = new File(localDBDir, user.getID() + ".db");
- messageCacheFile = new File(localDBDir, user.getID() + "_message_cache.db");
- statusCacheFile = new File(localDBDir, user.getID() + "_status_cache.db");
+ if (user == null) throw new IllegalStateException("Client user is null, cannot initialize user storage");
+ userFile = new File(dbDir, user.getID() + ".db");
}
- /**
- * {@inheritDoc}
- */
@Override
public void save() throws IOException {
// Save users
SerializationUtils.write(usersFile, users);
// Save user data
- if (user != null) {
- SerializationUtils.write(localDBFile, chats);
- SerializationUtils.write(messageCacheFile, messageCache);
- SerializationUtils.write(statusCacheFile, statusCache);
- }
+ if (user != null) SerializationUtils.write(userFile, chats, messageCache, statusCache);
// Save id generator
if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
}
- /**
- * {@inheritDoc}
- */
@Override
public void loadUsers() throws ClassNotFoundException, IOException { users = SerializationUtils.read(usersFile, HashMap.class); }
- /**
- * {@inheritDoc}
- */
@Override
public void loadUserData() throws ClassNotFoundException, IOException {
- chats = SerializationUtils.read(localDBFile, ArrayList.class);
- messageCache = SerializationUtils.read(messageCacheFile, Cache.class);
- statusCache = SerializationUtils.read(statusCacheFile, Cache.class);
+ try (var in = new ObjectInputStream(new FileInputStream(userFile))) {
+ chats = (ArrayList) in.readObject();
+ messageCache = (Cache) in.readObject();
+ statusCache = (Cache) in.readObject();
+ }
}
- /**
- * {@inheritDoc}
- */
@Override
public void loadIDGenerator() {
try {
diff --git a/src/main/java/envoy/client/data/Settings.java b/src/main/java/envoy/client/data/Settings.java
index c8bed6a..9d43bca 100644
--- a/src/main/java/envoy/client/data/Settings.java
+++ b/src/main/java/envoy/client/data/Settings.java
@@ -11,8 +11,8 @@ import envoy.util.SerializationUtils;
/**
* Manages all application settings, which are different objects that can be
* changed during runtime and serialized them by using either the file system or
- * the {@link Preferences} API.
- *
+ * the {@link Preferences} API.
+ *
* Project: envoy-client
* File: Settings.java
* Created: 11 Nov 2019
diff --git a/src/main/java/envoy/client/data/SettingsItem.java b/src/main/java/envoy/client/data/SettingsItem.java
index 8638c05..d74e222 100644
--- a/src/main/java/envoy/client/data/SettingsItem.java
+++ b/src/main/java/envoy/client/data/SettingsItem.java
@@ -7,8 +7,8 @@ import javax.swing.JComponent;
/**
* Encapsulates a persistent value that is directly or indirectly mutable by the
- * user.
- *
+ * user.
+ *
* Project: envoy-client
* File: SettingsItem.java
* Created: 23.12.2019
diff --git a/src/main/java/envoy/client/data/TransientLocalDB.java b/src/main/java/envoy/client/data/TransientLocalDB.java
index 1dcb0c4..5c7d2a6 100644
--- a/src/main/java/envoy/client/data/TransientLocalDB.java
+++ b/src/main/java/envoy/client/data/TransientLocalDB.java
@@ -2,8 +2,8 @@ package envoy.client.data;
/**
* Implements a {@link LocalDB} in a way that does not persist any information
- * after application shutdown.
- *
+ * after application shutdown.
+ *
* Project: envoy-client
* File: TransientLocalDB.java
* Created: 3 Feb 2020
@@ -11,5 +11,5 @@ package envoy.client.data;
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
-public class TransientLocalDB extends LocalDB {
+public final class TransientLocalDB extends LocalDB {
}
diff --git a/src/main/java/envoy/client/event/SendEvent.java b/src/main/java/envoy/client/event/SendEvent.java
index e3c9f97..8ea650b 100644
--- a/src/main/java/envoy/client/event/SendEvent.java
+++ b/src/main/java/envoy/client/event/SendEvent.java
@@ -8,7 +8,6 @@ import envoy.event.Event;
* Created: 11.02.2020
*
* @author: Maximilian Käfer
- *
* @since Envoy Client v0.3-alpha
*/
public class SendEvent extends Event> {
diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java
index f7f38a6..b4b1ed0 100644
--- a/src/main/java/envoy/client/net/Client.java
+++ b/src/main/java/envoy/client/net/Client.java
@@ -3,10 +3,8 @@ package envoy.client.net;
import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
import java.util.concurrent.TimeoutException;
+import java.util.logging.Level;
import java.util.logging.Logger;
import envoy.client.data.Cache;
@@ -15,15 +13,15 @@ import envoy.client.data.LocalDB;
import envoy.client.event.SendEvent;
import envoy.data.*;
import envoy.event.*;
-import envoy.event.contact.ContactOperationEvent;
+import envoy.event.contact.ContactOperation;
import envoy.event.contact.ContactSearchResult;
import envoy.util.EnvoyLog;
import envoy.util.SerializationUtils;
/**
* Establishes a connection to the server, performs a handshake and delivers
- * certain objects to the server.
- *
+ * certain objects to the server.
+ *
* Project: envoy-client
* File: Client.java
* Created: 28 Sep 2019
@@ -41,9 +39,8 @@ public class Client implements Closeable {
private boolean online;
// Asynchronously initialized during handshake
- private volatile User sender;
- private volatile Set extends Contact> contacts;
- private volatile boolean rejected;
+ private volatile User sender;
+ private volatile boolean rejected;
// Configuration, logging and event management
private static final ClientConfig config = ClientConfig.getInstance();
@@ -56,34 +53,38 @@ public class Client implements Closeable {
* will block for up to 5 seconds. If the handshake does exceed this time limit,
* an exception is thrown.
*
- * @param credentials the login credentials of the user
- * @param receivedMessageCache a message cache containing all unread messages
- * from the server that can be relayed after
- * initialization
- * @param receivedMessageStatusChangeEventCache an event cache containing all received messageStatusChangeEvents from the server that can be relayed after initialization
+ * @param credentials the login credentials of the user
+ * @param receivedMessageCache a message cache containing all unread
+ * messages
+ * from the server that can be relayed
+ * after
+ * initialization
+ * @param receivedMessageStatusChangeCache an event cache containing all
+ * received messageStatusChangeEvents
+ * from the server that can be relayed
+ * after initialization
* @throws TimeoutException if the server could not be reached
- * @throws IOException if the login credentials could not be
- * written
+ * @throws IOException if the login credentials could not be written
* @throws InterruptedException if the current thread is interrupted while
* waiting for the handshake response
*/
public void performHandshake(LoginCredentials credentials, Cache receivedMessageCache,
- Cache receivedMessageStatusChangeEventCache)
- throws TimeoutException, IOException, InterruptedException {
+ Cache receivedMessageStatusChangeCache) throws TimeoutException, IOException, InterruptedException {
if (online) throw new IllegalStateException("Handshake has already been performed successfully");
+
// Establish TCP connection
- logger.finer(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
+ logger.log(Level.FINER, String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
socket = new Socket(config.getServer(), config.getPort());
- logger.fine("Successfully established TCP connection to server");
+ logger.log(Level.FINE, "Successfully established TCP connection to server");
// Create object receiver
receiver = new Receiver(socket.getInputStream());
// Register user creation processor, contact list processor and message cache
- receiver.registerProcessor(User.class, sender -> { this.sender = sender; contacts = sender.getContacts(); });
+ receiver.registerProcessor(User.class, sender -> this.sender = sender);
receiver.registerProcessor(Message.class, receivedMessageCache);
- receiver.registerProcessor(MessageStatusChangeEvent.class, receivedMessageStatusChangeEventCache);
- receiver.registerProcessor(HandshakeRejectionEvent.class, evt -> { rejected = true; eventBus.dispatch(evt); });
+ receiver.registerProcessor(MessageStatusChange.class, receivedMessageStatusChangeCache);
+ receiver.registerProcessor(HandshakeRejection.class, evt -> { rejected = true; eventBus.dispatch(evt); });
rejected = false;
@@ -114,30 +115,36 @@ public class Client implements Closeable {
// Remove all processors as they are only used during the handshake
receiver.removeAllProcessors();
- logger.info("Handshake completed.");
+ logger.log(Level.INFO, "Handshake completed.");
}
/**
* Initializes the {@link Receiver} used to process data sent from the server to
* this client.
*
- * @param localDB the local database used to persist the current
- * {@link IDGenerator}
- * @param receivedMessageCache a message cache containing all unread messages
- * from the server that can be relayed after
- * initialization
- * @param receivedMessageStatusChangeEventCache an event cache containing all received messageStatusChangeEvents from the server that can be relayed after initialization
+ * @param localDB the local database used to persist
+ * the current
+ * {@link IDGenerator}
+ * @param receivedMessageCache a message cache containing all unread
+ * messages
+ * from the server that can be relayed
+ * after
+ * initialization
+ * @param receivedMessageStatusChangeCache an event cache containing all
+ * received messageStatusChangeEvents
+ * from the server that can be relayed
+ * after initialization
* @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, Cache receivedMessageCache,
- Cache receivedMessageStatusChangeEventCache) throws IOException {
+ public void initReceiver(LocalDB localDB, Cache receivedMessageCache, Cache receivedMessageStatusChangeCache)
+ throws IOException {
checkOnline();
// Process incoming messages
- final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor();
- final MessageStatusChangeEventProcessor messageStatusChangeEventProcessor = new MessageStatusChangeEventProcessor();
+ final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor();
+ final MessageStatusChangeProcessor messageStatusChangeEventProcessor = new MessageStatusChangeProcessor();
// TODO: Define a cache
receiver.registerProcessor(GroupMessage.class, new ReceivedGroupMessageProcessor());
@@ -148,33 +155,33 @@ public class Client implements Closeable {
receivedMessageCache.setProcessor(receivedMessageProcessor);
// Process message status changes
- receiver.registerProcessor(MessageStatusChangeEvent.class, messageStatusChangeEventProcessor);
- receivedMessageStatusChangeEventCache.setProcessor(messageStatusChangeEventProcessor);
+ receiver.registerProcessor(MessageStatusChange.class, messageStatusChangeEventProcessor);
+ receivedMessageStatusChangeCache.setProcessor(messageStatusChangeEventProcessor);
// Process user status changes
- receiver.registerProcessor(UserStatusChangeEvent.class, eventBus::dispatch);
+ receiver.registerProcessor(UserStatusChange.class, eventBus::dispatch);
// Process message ID generation
receiver.registerProcessor(IDGenerator.class, localDB::setIDGenerator);
// Process name changes
- receiver.registerProcessor(NameChangeEvent.class, evt -> { localDB.replaceContactName(evt); eventBus.dispatch(evt); });
+ receiver.registerProcessor(NameChange.class, evt -> { localDB.replaceContactName(evt); eventBus.dispatch(evt); });
// Process contact searches
receiver.registerProcessor(ContactSearchResult.class, eventBus::dispatch);
// Process contact operations
- receiver.registerProcessor(ContactOperationEvent.class, eventBus::dispatch);
+ receiver.registerProcessor(ContactOperation.class, eventBus::dispatch);
// Process group size changes
- receiver.registerProcessor(GroupResizeEvent.class, evt -> { localDB.updateGroup(evt); eventBus.dispatch(evt); });
+ receiver.registerProcessor(GroupResize.class, evt -> { localDB.updateGroup(evt); eventBus.dispatch(evt); });
// Send event
eventBus.register(SendEvent.class, evt -> {
try {
sendEvent(evt.get());
} catch (final IOException e) {
- e.printStackTrace();
+ logger.log(Level.WARNING, "An error occurred when trying to send " + evt, e);
}
});
@@ -221,36 +228,23 @@ public class Client implements Closeable {
* @since Envoy Client v0.3-alpha
*/
public void requestIdGenerator() throws IOException {
- logger.info("Requesting new id generator...");
+ logger.log(Level.INFO, "Requesting new id generator...");
writeObject(new IDGeneratorRequest());
}
- /**
- * @return a {@code Map} of all users on the server with their
- * user names as keys
- * @since Envoy Client v0.2-alpha
- */
- public Map getUsers() {
- checkOnline();
- final Map users = new HashMap<>();
- contacts.forEach(u -> users.put(u.getName(), u));
- users.put(sender.getName(), sender);
- return users;
- }
-
@Override
public void close() throws IOException { if (online) socket.close(); }
private void writeObject(Object obj) throws IOException {
checkOnline();
- logger.fine("Sending " + obj);
+ logger.log(Level.FINE, "Sending " + obj);
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
}
private void checkOnline() { if (!online) throw new IllegalStateException("Client is not online"); }
/**
- * @return the sender object that represents this client.
+ * @return the {@link User} as which this client is logged in
* @since Envoy Client v0.1-alpha
*/
public User getSender() { return sender; }
@@ -261,7 +255,7 @@ public class Client implements Closeable {
* @param clientUser the client user to set
* @since Envoy Client v0.2-alpha
*/
- public void setSender(User clientUser) { this.sender = clientUser; }
+ public void setSender(User clientUser) { sender = clientUser; }
/**
* @return the {@link Receiver} used by this {@link Client}
@@ -273,16 +267,4 @@ public class Client implements Closeable {
* @since Envoy Client v0.2-alpha
*/
public boolean isOnline() { return online; }
-
- /**
- * @return the contacts of this {@link Client}
- * @since Envoy Client v0.3-alpha
- */
- public Set extends Contact> getContacts() { return contacts; }
-
- /**
- * @param contacts the contacts to set
- * @since Envoy Client v0.3-alpha
- */
- public void setContacts(Set extends Contact> contacts) { this.contacts = contacts; }
}
diff --git a/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java b/src/main/java/envoy/client/net/MessageStatusChangeProcessor.java
similarity index 67%
rename from src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java
rename to src/main/java/envoy/client/net/MessageStatusChangeProcessor.java
index ce53139..106cda4 100644
--- a/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java
+++ b/src/main/java/envoy/client/net/MessageStatusChangeProcessor.java
@@ -5,30 +5,30 @@ import java.util.logging.Logger;
import envoy.data.Message.MessageStatus;
import envoy.event.EventBus;
-import envoy.event.MessageStatusChangeEvent;
+import envoy.event.MessageStatusChange;
import envoy.util.EnvoyLog;
/**
* Project: envoy-client
- * File: MessageStatusChangeEventProcessor.java
+ * File: MessageStatusChangeProcessor.java
* Created: 4 Feb 2020
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
-public class MessageStatusChangeEventProcessor implements Consumer {
+public class MessageStatusChangeProcessor implements Consumer {
- private static final Logger logger = EnvoyLog.getLogger(MessageStatusChangeEventProcessor.class);
+ private static final Logger logger = EnvoyLog.getLogger(MessageStatusChangeProcessor.class);
/**
- * Dispatches a {@link MessageStatusChangeEvent} if the status is
+ * Dispatches a {@link MessageStatusChange} if the status is
* {@code RECEIVED} or {@code READ}.
*
* @param evt the status change event
* @since Envoy Client v0.3-alpha
*/
@Override
- public void accept(MessageStatusChangeEvent evt) {
+ public void accept(MessageStatusChange evt) {
if (evt.get().ordinal() < MessageStatus.RECEIVED.ordinal()) logger.warning("Received invalid message status change " + evt);
else EventBus.getInstance().dispatch(evt);
}
diff --git a/src/main/java/envoy/client/net/ReceivedMessageProcessor.java b/src/main/java/envoy/client/net/ReceivedMessageProcessor.java
index 29de04a..f83815c 100644
--- a/src/main/java/envoy/client/net/ReceivedMessageProcessor.java
+++ b/src/main/java/envoy/client/net/ReceivedMessageProcessor.java
@@ -1,6 +1,7 @@
package envoy.client.net;
import java.util.function.Consumer;
+import java.util.logging.Level;
import java.util.logging.Logger;
import envoy.client.event.MessageCreationEvent;
@@ -23,7 +24,7 @@ public class ReceivedMessageProcessor implements Consumer {
@Override
public void accept(Message message) {
- if (message.getStatus() != MessageStatus.SENT) logger.warning("The message has the unexpected status " + message.getStatus());
+ if (message.getStatus() != MessageStatus.SENT) logger.log(Level.WARNING, "The message has the unexpected status " + message.getStatus());
else {
// Update status to RECEIVED
message.nextStatus();
diff --git a/src/main/java/envoy/client/net/Receiver.java b/src/main/java/envoy/client/net/Receiver.java
index 5926e1e..c71e5ed 100644
--- a/src/main/java/envoy/client/net/Receiver.java
+++ b/src/main/java/envoy/client/net/Receiver.java
@@ -45,7 +45,7 @@ public class Receiver extends Thread {
/**
* Starts the receiver loop. When an object is read, it is passed to the
* appropriate processor.
- *
+ *
* @since Envoy Client v0.3-alpha
*/
@Override
@@ -54,31 +54,31 @@ public class Receiver extends Thread {
try {
while (true) {
// Read object length
- byte[] lenBytes = new byte[4];
+ final byte[] lenBytes = new byte[4];
in.read(lenBytes);
- int len = SerializationUtils.bytesToInt(lenBytes, 0);
+ final int len = SerializationUtils.bytesToInt(lenBytes, 0);
// Read object into byte array
- byte[] objBytes = new byte[len];
+ final byte[] objBytes = new byte[len];
in.read(objBytes);
try (ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(objBytes))) {
- Object obj = oin.readObject();
- logger.fine("Received " + obj);
+ final Object obj = oin.readObject();
+ logger.log(Level.FINE, "Received " + obj);
// Get appropriate processor
@SuppressWarnings("rawtypes")
- Consumer processor = processors.get(obj.getClass());
+ final Consumer processor = processors.get(obj.getClass());
if (processor == null)
- logger.warning(String.format("The received object has the class %s for which no processor is defined.", obj.getClass()));
+ logger.log(Level.WARNING, String.format(
+ "The received object has the class %s for which no processor is defined.", obj.getClass()));
else processor.accept(obj);
}
}
- } catch (SocketException e) {
+ } catch (final SocketException e) {
// Connection probably closed by client.
- } catch (Exception e) {
+ } catch (final Exception e) {
logger.log(Level.SEVERE, "Error on receiver thread", e);
- e.printStackTrace();
}
}
@@ -94,7 +94,7 @@ public class Receiver extends Thread {
/**
* Removes all object processors registered at this {@link Receiver}.
- *
+ *
* @since Envoy Client v0.3-alpha
*/
public void removeAllProcessors() { processors.clear(); }
diff --git a/src/main/java/envoy/client/net/WriteProxy.java b/src/main/java/envoy/client/net/WriteProxy.java
index 97848df..339f1f2 100644
--- a/src/main/java/envoy/client/net/WriteProxy.java
+++ b/src/main/java/envoy/client/net/WriteProxy.java
@@ -6,14 +6,14 @@ import java.util.logging.Logger;
import envoy.client.data.LocalDB;
import envoy.data.Message;
-import envoy.event.MessageStatusChangeEvent;
+import envoy.event.MessageStatusChange;
import envoy.util.EnvoyLog;
/**
* Implements methods to send {@link Message}s and
- * {@link MessageStatusChangeEvent}s to the server or cache them inside a
- * {@link LocalDB} depending on the online status.
- *
+ * {@link MessageStatusChange}s to the server or cache them inside a
+ * {@link LocalDB} depending on the online status.
+ *
* Project: envoy-client
* File: WriteProxy.java
* Created: 6 Feb 2020
@@ -45,27 +45,24 @@ public class WriteProxy {
// Initialize cache processors for messages and message status change events
localDB.getMessageCache().setProcessor(msg -> {
try {
- logger.finer("Sending cached " + msg);
+ logger.log(Level.FINER, "Sending cached " + msg);
client.sendMessage(msg);
-
- // Update message state to SENT in localDB
- localDB.getMessage(msg.getID()).ifPresent(Message::nextStatus);
- } catch (IOException e) {
- logger.log(Level.SEVERE, "Could not send cached message", e);
+ } catch (final IOException e) {
+ logger.log(Level.SEVERE, "Could not send cached message: ", e);
}
});
localDB.getStatusCache().setProcessor(evt -> {
- logger.finer("Sending cached " + evt);
+ logger.log(Level.FINER, "Sending cached " + evt);
try {
client.sendEvent(evt);
- } catch (IOException e) {
- logger.log(Level.SEVERE, "Could not send cached message status change event", e);
+ } catch (final IOException e) {
+ logger.log(Level.SEVERE, "Could not send cached message status change event: ", e);
}
});
}
/**
- * Sends cached {@link Message}s and {@link MessageStatusChangeEvent}s to the
+ * Sends cached {@link Message}s and {@link MessageStatusChange}s to the
* server.
*
* @since Envoy Client v0.3-alpha
@@ -99,7 +96,7 @@ public class WriteProxy {
* @throws IOException if the event could not be sent
* @since Envoy Client v0.3-alpha
*/
- public void writeMessageStatusChangeEvent(MessageStatusChangeEvent evt) throws IOException {
+ public void writeMessageStatusChange(MessageStatusChange evt) throws IOException {
if (client.isOnline()) client.sendEvent(evt);
else localDB.getStatusCache().accept(evt);
}
diff --git a/src/main/java/envoy/client/ui/ContactListCell.java b/src/main/java/envoy/client/ui/ContactListCell.java
index 5cb04ca..283a89a 100644
--- a/src/main/java/envoy/client/ui/ContactListCell.java
+++ b/src/main/java/envoy/client/ui/ContactListCell.java
@@ -3,7 +3,6 @@ package envoy.client.ui;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.VBox;
-import javafx.scene.paint.Color;
import envoy.data.Contact;
import envoy.data.Group;
@@ -32,32 +31,19 @@ public class ContactListCell extends ListCell {
setText(null);
setGraphic(null);
} else {
- // the infoLabel displays specific contact info, i.e. status of a user or amount
- // of members in a group
- Label infoLabel = null;
+ // Container with contact name
+ final var vbox = new VBox(new Label(contact.getName()));
if (contact instanceof User) {
- // user specific info
- infoLabel = new Label(((User) contact).getStatus().toString());
- Color textColor = null;
- switch (((User) contact).getStatus()) {
- case ONLINE:
- textColor = Color.LIMEGREEN;
- break;
- case AWAY:
- textColor = Color.ORANGERED;
- break;
- case BUSY:
- textColor = Color.RED;
- break;
- case OFFLINE:
- textColor = Color.GRAY;
- break;
- }
- infoLabel.setTextFill(textColor);
- } else
- // group specific infos
- infoLabel = new Label(String.valueOf(((Group) contact).getContacts().size()) + " members");
- setGraphic(new VBox(new Label(contact.getName()), infoLabel));
+ // Online status
+ final var user = (User) contact;
+ final var statusLabel = new Label(user.getStatus().toString());
+ statusLabel.getStyleClass().add(user.getStatus().toString().toLowerCase());
+ vbox.getChildren().add(statusLabel);
+ } else {
+ // Member count
+ vbox.getChildren().add(new Label(((Group) contact).getContacts().size() + " members"));
+ }
+ setGraphic(vbox);
}
}
}
diff --git a/src/main/java/envoy/client/ui/IconUtil.java b/src/main/java/envoy/client/ui/IconUtil.java
index e2577ec..b477e53 100644
--- a/src/main/java/envoy/client/ui/IconUtil.java
+++ b/src/main/java/envoy/client/ui/IconUtil.java
@@ -1,6 +1,5 @@
package envoy.client.ui;
-import java.io.IOException;
import java.util.EnumMap;
import java.util.EnumSet;
@@ -8,11 +7,11 @@ import javafx.scene.image.Image;
/**
* Provides static utility methods for loading icons from the resource
- * folder.
- *
- * Project: envoy-client
- * File: IconUtil.java
- * Created: 16.03.2020
+ * folder.
+ *
+ * Project: envoy-client
+ * File: IconUtil.java
+ * Created: 16.03.2020
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
@@ -23,46 +22,43 @@ public class IconUtil {
/**
* Loads an icon from the resource folder.
- *
+ *
* @param path the path to the icon inside the resource folder
* @return the icon
- * @throws IOException if the loading process failed
* @since Envoy Client v0.1-beta
*/
- public static Image load(String path) throws IOException { return new Image(IconUtil.class.getResource(path).toExternalForm()); }
+ public static Image load(String path) { return new Image(IconUtil.class.getResource(path).toExternalForm()); }
/**
* Loads an icon from the resource folder and scales it to a given size.
- *
+ *
* @param path the path to the icon inside the resource folder
* @param size the size to scale the icon to
* @return the scaled icon
- * @throws IOException if the loading process failed
* @since Envoy Client v0.1-beta
*/
- public static Image load(String path, int size) throws IOException {
+ public static Image load(String path, int size) {
return new Image(IconUtil.class.getResource(path).toExternalForm(), size, size, true, true);
}
/**
- *
+ *
* Loads icons specified by an enum. The images have to be named like the
* lowercase enum constants with {@code .png} extension and be located inside a
* folder with the lowercase name of the enum, which must be contained inside
* the {@code /icons} folder.
- *
+ *
* @param the enum that specifies the icons to load
* @param enumClass the class of the enum
* @param size the size to scale the icons to
* @return a map containing the loaded icons with the corresponding enum
* constants as keys
- * @throws IOException if the loading process failed
* @since Envoy Client v0.1-beta
*/
- public static > EnumMap loadByEnum(Class enumClass, int size) throws IOException {
- var icons = new EnumMap(enumClass);
- var path = "/icons/" + enumClass.getSimpleName().toLowerCase() + "/";
- for (var e : EnumSet.allOf(enumClass))
+ public static > EnumMap loadByEnum(Class enumClass, int size) {
+ final var icons = new EnumMap(enumClass);
+ final var path = "/icons/" + enumClass.getSimpleName().toLowerCase() + "/";
+ for (final var e : EnumSet.allOf(enumClass))
icons.put(e, load(path + e.toString().toLowerCase() + ".png", size));
return icons;
}
diff --git a/src/main/java/envoy/client/ui/MessageListCell.java b/src/main/java/envoy/client/ui/MessageListCell.java
index 7b7a479..21a094e 100644
--- a/src/main/java/envoy/client/ui/MessageListCell.java
+++ b/src/main/java/envoy/client/ui/MessageListCell.java
@@ -1,18 +1,18 @@
package envoy.client.ui;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
+import java.time.format.DateTimeFormatter;
import java.util.Map;
+import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
-import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
+import envoy.data.User;
/**
* Displays a single message inside the message list.
@@ -20,40 +20,41 @@ import envoy.data.Message.MessageStatus;
* Project: envoy-client
* File: MessageListCell.java
* Created: 28.03.2020
- *
+ *
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class MessageListCell extends ListCell {
- private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
- private static Map statusImages;
-
- static {
- try {
- statusImages = IconUtil.loadByEnum(MessageStatus.class, 32);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
+ private static User client;
+ private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
+ private static final Map statusImages = IconUtil.loadByEnum(MessageStatus.class, 16);
/**
* Displays the text, the data of creation and the status of a message.
- *
+ *
* @since Envoy v0.1-beta
*/
@Override
protected void updateItem(Message message, boolean empty) {
super.updateItem(message, empty);
- if(empty || message == null) {
+ if (empty || message == null) {
setText(null);
setGraphic(null);
} else {
- setGraphic(new HBox(
- new VBox(
- new Label(dateFormat.format(message.getCreationDate())),
- new Label(message.getText())),
- new Label("", new ImageView(statusImages.get(message.getStatus())))));
+ final var cell = new VBox(new Label(dateFormat.format(message.getCreationDate())), new Label(message.getText()));
+ if (message.getRecipientID() == client.getID()) {
+ cell.getChildren().add(new Label("", new ImageView(statusImages.get(message.getStatus()))));
+ cell.getStyleClass().add("own-message");
+ } else cell.getStyleClass().add("received-message");
+ cell.paddingProperty().setValue(new Insets(5, 20, 5, 20));
+ setGraphic(cell);
}
}
+
+ /**
+ * @param client the user who chats with another person
+ * @since Envoy Client v0.1-beta
+ */
+ public static void setUser(User client) { MessageListCell.client = client; }
}
diff --git a/src/main/java/envoy/client/ui/SceneContext.java b/src/main/java/envoy/client/ui/SceneContext.java
index ea3b4b2..52c847b 100644
--- a/src/main/java/envoy/client/ui/SceneContext.java
+++ b/src/main/java/envoy/client/ui/SceneContext.java
@@ -2,6 +2,7 @@ package envoy.client.ui;
import java.io.IOException;
import java.util.Stack;
+import java.util.logging.Level;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
@@ -11,6 +12,7 @@ import javafx.stage.Stage;
import envoy.client.data.Settings;
import envoy.client.event.ThemeChangeEvent;
import envoy.event.EventBus;
+import envoy.util.EnvoyLog;
/**
* Manages a stack of scenes. The most recently added scene is displayed inside
@@ -23,7 +25,7 @@ import envoy.event.EventBus;
* Project: envoy-client
* File: SceneContext.java
* Created: 06.06.2020
- *
+ *
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
@@ -31,43 +33,43 @@ public final class SceneContext {
/**
* Contains information about different scenes and their FXML resource files.
- *
+ *
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
- public static enum SceneInfo {
+ public enum SceneInfo {
/**
* The main scene in which chats are displayed.
- *
+ *
* @since Envoy Client v0.1-beta
*/
CHAT_SCENE("/fxml/ChatScene.fxml"),
/**
* The scene in which settings are displayed.
- *
+ *
* @since Envoy Client v0.1-beta
*/
SETTINGS_SCENE("/fxml/SettingsScene.fxml"),
/**
* The scene in which the contact search is displayed.
- *
+ *
* @since Envoy Client v0.1-beta
*/
CONTACT_SEARCH_SCENE("/fxml/ContactSearchScene.fxml"),
/**
* The scene in which the group creation screen is displayed.
- *
+ *
* @since Envoy Client v0.1-beta
*/
GROUP_CREATION_SCENE("/fxml/GroupCreationScene.fxml"),
/**
* The scene in which the login screen is displayed.
- *
+ *
* @since Envoy Client v0.1-beta
*/
LOGIN_SCENE("/fxml/LoginScene.fxml");
@@ -88,7 +90,7 @@ public final class SceneContext {
/**
* Initializes the scene context.
- *
+ *
* @param stage the stage in which scenes will be displayed
* @since Envoy Client v0.1-beta
*/
@@ -99,7 +101,7 @@ public final class SceneContext {
/**
* Loads a new scene specified by a scene info.
- *
+ *
* @param sceneInfo specifies the scene to load
* @throws RuntimeException if the loading process fails
* @since Envoy Client v0.1-beta
@@ -117,14 +119,15 @@ public final class SceneContext {
applyCSS();
stage.sizeToScene();
stage.show();
- } catch (IOException e) {
+ } catch (final IOException e) {
+ EnvoyLog.getLogger(SceneContext.class).log(Level.SEVERE, String.format("Could not load scene for %s: ", sceneInfo), e);
throw new RuntimeException(e);
}
}
/**
* Removes the current scene and displays the previous one.
- *
+ *
* @since Envoy Client v0.1-beta
*/
public void pop() {
diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java
index dbadfc3..f57b22e 100644
--- a/src/main/java/envoy/client/ui/Startup.java
+++ b/src/main/java/envoy/client/ui/Startup.java
@@ -16,7 +16,7 @@ import envoy.client.net.Client;
import envoy.client.ui.SceneContext.SceneInfo;
import envoy.client.ui.controller.LoginScene;
import envoy.data.Message;
-import envoy.event.MessageStatusChangeEvent;
+import envoy.event.MessageStatusChange;
import envoy.exception.EnvoyException;
import envoy.util.EnvoyLog;
@@ -33,10 +33,17 @@ import envoy.util.EnvoyLog;
*/
public final class Startup extends Application {
- private LocalDB localDB;
- private Client client;
- private Cache messageCache;
- private Cache messageStatusCache;
+ /**
+ * The version of this client. Used to verify compatibility with the server.
+ *
+ * @since Envoy Client v0.1-beta
+ */
+ public static final String VERSION = "0.1-beta";
+
+ private LocalDB localDB;
+ private Client client;
+ private Cache messageCache;
+ private Cache messageStatusCache;
private static final ClientConfig config = ClientConfig.getInstance();
private static final Logger logger = EnvoyLog.getLogger(Startup.class);
@@ -44,7 +51,7 @@ public final class Startup extends Application {
/**
* Loads the configuration, initializes the client and the local database and
* delegates the rest of the startup process to {@link LoginScene}.
- *
+ *
* @since Envoy Client v0.1-beta
*/
@Override
@@ -63,6 +70,7 @@ public final class Startup extends Application {
if (!config.isInitialized()) throw new EnvoyException("Configuration is not fully initialized");
} catch (final Exception e) {
new Alert(AlertType.ERROR, "Error loading configuration values:\n" + e);
+ logger.log(Level.SEVERE, "Error loading configuration values: ", e);
e.printStackTrace();
System.exit(1);
}
@@ -73,24 +81,24 @@ public final class Startup extends Application {
EnvoyLog.setFileLevelBarrier(config.getFileLevelBarrier());
EnvoyLog.setConsoleLevelBarrier(config.getConsoleLevelBarrier());
+ logger.log(Level.INFO, "Envoy starting...");
+
// Initialize the local database
if (config.isIgnoreLocalDB()) {
localDB = new TransientLocalDB();
new Alert(AlertType.WARNING, "Ignoring local database.\nMessages will not be saved!").showAndWait();
- } else {
- try {
- localDB = new PersistentLocalDB(new File(config.getHomeDirectory(), config.getLocalDB().getPath()));
- } catch (final IOException e3) {
- logger.log(Level.SEVERE, "Could not initialize local database", e3);
- new Alert(AlertType.ERROR, "Could not initialize local database!\n" + e3).showAndWait();
- System.exit(1);
- return;
- }
+ } else try {
+ localDB = new PersistentLocalDB(new File(config.getHomeDirectory(), config.getLocalDB().getPath()));
+ } catch (final IOException e3) {
+ logger.log(Level.SEVERE, "Could not initialize local database: ", e3);
+ new Alert(AlertType.ERROR, "Could not initialize local database!\n" + e3).showAndWait();
+ System.exit(1);
+ return;
}
// Initialize client and unread message cache
- client = new Client();
- messageCache = new Cache<>();
+ client = new Client();
+ messageCache = new Cache<>();
messageStatusCache = new Cache<>();
stage.setTitle("Envoy");
@@ -103,20 +111,21 @@ public final class Startup extends Application {
/**
* Closes the client connection and saves the local database and settings.
- *
+ *
* @since Envoy Client v0.1-beta
*/
@Override
public void stop() {
try {
- logger.info("Closing connection...");
+ logger.log(Level.INFO, "Closing connection...");
client.close();
- logger.info("Saving local database and settings...");
+ logger.log(Level.INFO, "Saving local database and settings...");
localDB.save();
Settings.getInstance().save();
+ logger.log(Level.INFO, "Envoy was terminated by its user");
} catch (final Exception e) {
- logger.log(Level.SEVERE, "Unable to save local files", e);
+ logger.log(Level.SEVERE, "Unable to save local files: ", e);
}
}
diff --git a/src/main/java/envoy/client/ui/StatusTrayIcon.java b/src/main/java/envoy/client/ui/StatusTrayIcon.java
index 621655f..eb0178d 100644
--- a/src/main/java/envoy/client/ui/StatusTrayIcon.java
+++ b/src/main/java/envoy/client/ui/StatusTrayIcon.java
@@ -4,11 +4,13 @@ import java.awt.*;
import java.awt.TrayIcon.MessageType;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
+import java.util.logging.Level;
import envoy.client.event.MessageCreationEvent;
import envoy.data.Message;
import envoy.event.EventBus;
import envoy.exception.EnvoyException;
+import envoy.util.EnvoyLog;
/**
* Project: envoy-client
@@ -25,7 +27,7 @@ public class StatusTrayIcon {
* system tray. This includes displaying the icon, but also creating
* notifications when new messages are received.
*/
- private TrayIcon trayIcon;
+ private final TrayIcon trayIcon;
/**
* A received {@link Message} is only displayed as a system tray notification if
@@ -46,16 +48,16 @@ public class StatusTrayIcon {
public StatusTrayIcon(Window focusTarget) throws EnvoyException {
if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported.");
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- Image img = Toolkit.getDefaultToolkit().createImage(loader.getResource("envoy_logo.png"));
+ final ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ final Image img = Toolkit.getDefaultToolkit().createImage(loader.getResource("envoy_logo.png"));
trayIcon = new TrayIcon(img, "Envoy Client");
trayIcon.setImageAutoSize(true);
trayIcon.setToolTip("You are notified if you have unread messages.");
- PopupMenu popup = new PopupMenu();
+ final PopupMenu popup = new PopupMenu();
- MenuItem exitMenuItem = new MenuItem("Exit");
- exitMenuItem.addActionListener((evt) -> System.exit(0));
+ final MenuItem exitMenuItem = new MenuItem("Exit");
+ exitMenuItem.addActionListener(evt -> System.exit(0));
popup.add(exitMenuItem);
trayIcon.setPopupMenu(popup);
@@ -71,7 +73,7 @@ public class StatusTrayIcon {
});
// Show the window if the user clicks on the icon
- trayIcon.addActionListener((evt) -> { focusTarget.setVisible(true); focusTarget.requestFocus(); });
+ trayIcon.addActionListener(evt -> { focusTarget.setVisible(true); focusTarget.requestFocus(); });
// Start processing message events
// TODO: Handle other message types
@@ -90,7 +92,8 @@ public class StatusTrayIcon {
public void show() throws EnvoyException {
try {
SystemTray.getSystemTray().add(trayIcon);
- } catch (AWTException e) {
+ } catch (final AWTException e) {
+ EnvoyLog.getLogger(StatusTrayIcon.class).log(Level.INFO, "Could not display StatusTrayIcon: ", e);
throw new EnvoyException("Could not attach Envoy tray icon to system tray.", e);
}
}
diff --git a/src/main/java/envoy/client/ui/controller/ChatScene.java b/src/main/java/envoy/client/ui/controller/ChatScene.java
index 48347e7..a092234 100644
--- a/src/main/java/envoy/client/ui/controller/ChatScene.java
+++ b/src/main/java/envoy/client/ui/controller/ChatScene.java
@@ -1,5 +1,7 @@
package envoy.client.ui.controller;
+import java.awt.Toolkit;
+import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -10,9 +12,11 @@ import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
+import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
import envoy.client.data.Chat;
import envoy.client.data.LocalDB;
@@ -20,14 +24,12 @@ import envoy.client.data.Settings;
import envoy.client.event.MessageCreationEvent;
import envoy.client.net.Client;
import envoy.client.net.WriteProxy;
-import envoy.client.ui.ContactListCell;
-import envoy.client.ui.MessageListCell;
-import envoy.client.ui.SceneContext;
+import envoy.client.ui.*;
import envoy.data.*;
import envoy.event.EventBus;
-import envoy.event.MessageStatusChangeEvent;
-import envoy.event.UserStatusChangeEvent;
-import envoy.event.contact.ContactOperationEvent;
+import envoy.event.MessageStatusChange;
+import envoy.event.UserStatusChange;
+import envoy.event.contact.ContactOperation;
import envoy.util.EnvoyLog;
/**
@@ -61,11 +63,19 @@ public final class ChatScene {
@FXML
private Label remainingChars;
+ @FXML
+ private Label infoLabel;
+
+ @FXML
+ private MenuItem deleteContactMenuItem;
+
private LocalDB localDB;
private Client client;
private WriteProxy writeProxy;
private SceneContext sceneContext;
+ private boolean postingPermanentlyDisabled = false;
+
private Chat currentChat;
private static final Settings settings = Settings.getInstance();
@@ -85,6 +95,8 @@ public final class ChatScene {
messageList.setCellFactory(listView -> new MessageListCell());
userList.setCellFactory(listView -> new ContactListCell());
+ settingsButton.setGraphic(new ImageView(IconUtil.load("/icons/settings.png", 16)));
+
// Listen to received messages
eventBus.register(MessageCreationEvent.class, e -> {
final var message = e.get();
@@ -100,16 +112,20 @@ public final class ChatScene {
localDB.getChat(message.getRecipientID()).ifPresent(chat -> {
chat.getMessages().add(message);
- // Update UI if in current chat
- if (chat == currentChat)
- Platform.runLater(messageList::refresh);
+ if (chat.equals(currentChat)) {
+ try {
+ currentChat.read(writeProxy);
+ } catch (final IOException e1) {
+ logger.log(Level.WARNING, "Could not read current chat: ", e1);
+ }
+ Platform.runLater(() -> { messageList.refresh(); scrollToMessageListEnd(); });
+ }
});
}
});
// Listen to message status changes
- eventBus.register(MessageStatusChangeEvent.class, e ->
- localDB.getMessage(e.getID()).ifPresent(message -> {
+ eventBus.register(MessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(message -> {
message.setStatus(e.get());
// Update UI if in current chat
@@ -117,19 +133,15 @@ public final class ChatScene {
}));
// Listen to user status changes
- eventBus.register(UserStatusChangeEvent.class, e ->
- userList.getItems()
- .stream()
- .filter(c -> c.getID() == e.getID())
- .findAny()
- .ifPresent(u -> {
- ((User) u).setStatus(e.get());
- Platform.runLater(userList::refresh);
- })
- );
+ eventBus.register(UserStatusChange.class,
+ e -> userList.getItems()
+ .stream()
+ .filter(c -> c.getID() == e.getID())
+ .findAny()
+ .ifPresent(u -> { ((User) u).setStatus(e.get()); Platform.runLater(userList::refresh); }));
// Listen to contacts changes
- eventBus.register(ContactOperationEvent.class, e -> {
+ eventBus.register(ContactOperation.class, e -> {
final var contact = e.get();
switch (e.getOperationType()) {
case ADD:
@@ -163,6 +175,9 @@ public final class ChatScene {
this.writeProxy = writeProxy;
userList.setItems(FXCollections.observableList(localDB.getChats().stream().map(Chat::getRecipient).collect(Collectors.toList())));
+ contactLabel.setText(localDB.getUser().getName());
+ MessageListCell.setUser(localDB.getUser());
+ if (!client.isOnline()) updateInfoLabel("You are offline", Color.YELLOW);
}
/**
@@ -173,23 +188,29 @@ public final class ChatScene {
@FXML
private void userListClicked() {
final Contact user = userList.getSelectionModel().getSelectedItem();
- if (user != null && (currentChat == null || user.getID() != currentChat.getRecipient().getID())) {
- contactLabel.setText(user.getName());
+ if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
+ logger.log(Level.FINEST, "Loading chat with " + user);
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
- // Load the chat or create a new one and add it to the LocalDB
- currentChat = localDB
- .getChat(user.getID())
- .orElseGet(() -> { final var chat = new Chat(user); localDB.getChats().add(chat); return chat; });
+ // Load the chat
+ currentChat = localDB.getChat(user.getID()).get();
messageList.setItems(FXCollections.observableList(currentChat.getMessages()));
+ deleteContactMenuItem.setText("Delete " + user.getName());
+
+ // Read the current chat
+ try {
+ currentChat.read(writeProxy);
+ } catch (final IOException e) {
+ logger.log(Level.WARNING, "Could not read current chat.", e);
+ }
remainingChars.setVisible(true);
remainingChars
.setText(String.format("remaining chars: %d/%d", MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
}
- messageTextArea.setDisable(currentChat == null);
+ messageTextArea.setDisable(currentChat == null || postingPermanentlyDisabled);
}
/**
@@ -214,6 +235,42 @@ public final class ChatScene {
sceneContext.getController().initializeData(sceneContext, localDB);
}
+ /**
+ * Checks the text length of the {@code messageTextArea}, adjusts the
+ * {@code remainingChars} label and checks whether to send the message
+ * automatically.
+ *
+ * @param e the key event that will be analyzed for a post request
+ * @since Envoy Client v0.1-beta
+ */
+ @FXML
+ private void checkKeyCombination(KeyEvent e) {
+ // Checks whether the text is too long
+ messageTextUpdated();
+ // Automatic sending of messages via (ctrl +) enter
+ checkPostConditions(e);
+ }
+
+ /**
+ * @param e the keys that have been pressed
+ * @since Envoy Client v0.1-beta
+ */
+ @FXML
+ private void checkPostConditions(KeyEvent e) {
+ if (!postingPermanentlyDisabled) {
+ if (!postButton.isDisabled() && (settings.isEnterToSend() && e.getCode() == KeyCode.ENTER
+ || !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown()))
+ postMessage();
+ postButton.setDisable(messageTextArea.getText().isBlank() || currentChat == null);
+ } else {
+ final var noMoreMessaging = "Go online to send messages";
+ if (!infoLabel.getText().equals(noMoreMessaging))
+ // Informing the user that he is a f*cking moron and should use Envoy online
+ // because he ran out of messageIDs to use
+ updateInfoLabel(noMoreMessaging, Color.RED);
+ }
+ }
+
/**
* Actions to perform when the text was updated in the messageTextArea.
*
@@ -227,29 +284,21 @@ public final class ChatScene {
messageTextArea.positionCaret(MAX_MESSAGE_LENGTH);
messageTextArea.setScrollTop(Double.MAX_VALUE);
}
+ updateRemainingCharsLabel();
+ }
- // Redesigning the remainingChars - Label
+ /**
+ * Sets the text and text color of the {@code remainingChars} label.
+ *
+ * @since Envoy Client v0.1-beta
+ */
+ private void updateRemainingCharsLabel() {
final int currentLength = messageTextArea.getText().length();
final int remainingLength = MAX_MESSAGE_LENGTH - currentLength;
remainingChars.setText(String.format("remaining chars: %d/%d", remainingLength, MAX_MESSAGE_LENGTH));
remainingChars.setTextFill(Color.rgb(currentLength, remainingLength, 0, 1));
}
- /**
- * Actions to perform when a key has been entered.
- *
- * @param e the Keys that have been entered
- * @since Envoy Client v0.1-beta
- */
- @FXML
- private void checkKeyCombination(KeyEvent e) {
- // Automatic sending of messages via (ctrl +) enter
- if (!postButton.isDisabled() && settings.isEnterToSend() && e.getCode() == KeyCode.ENTER
- || !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown())
- postMessage();
- postButton.setDisable(messageTextArea.getText().isBlank() || currentChat == null);
- }
-
/**
* Sends a new message or groupMessage to the server based on the text entered
* in the
@@ -259,6 +308,16 @@ public final class ChatScene {
*/
@FXML
private void postMessage() {
+ postingPermanentlyDisabled = !(client.isOnline() || localDB.getIDGenerator().hasNext());
+ if (postingPermanentlyDisabled) {
+ postButton.setDisable(true);
+ messageTextArea.setDisable(true);
+ messageTextArea.clear();
+ updateInfoLabel("You need to go online to send more messages", Color.RED);
+ return;
+ }
+ final var text = messageTextArea.getText().strip();
+ if (text.isBlank()) throw new IllegalArgumentException("A message without visible text can not be sent.");
try {
if (currentChat.getRecipient().getClass().equals(Group.class)) {
// Create and send groupMessage
@@ -283,17 +342,72 @@ public final class ChatScene {
// Add message to LocalDB and update UI
messageList.getItems().add(message);
}
+ scrollToMessageListEnd();
// Request a new ID generator if all IDs were used
if (!localDB.getIDGenerator().hasNext() && client.isOnline()) client.requestIdGenerator();
} catch (final IOException e) {
- logger.log(Level.SEVERE, "Error sending message", e);
+ logger.log(Level.SEVERE, "Error while sending message: ", e);
new Alert(AlertType.ERROR, "An error occured while sending the message!").showAndWait();
}
// Clear text field and disable post button
messageTextArea.setText("");
postButton.setDisable(true);
+ updateRemainingCharsLabel();
+ }
+
+ /**
+ * Scrolls to the bottom of the {@code messageList}.
+ *
+ * @since Envoy Client v0.1-beta
+ */
+ private void scrollToMessageListEnd() { messageList.scrollTo(messageList.getItems().size() - 1); }
+
+ /**
+ * Updates the {@code infoLabel}.
+ *
+ * @param text the text to use
+ * @param textfill the color in which to display information
+ * @since Envoy Client v0.1-beta
+ */
+ private void updateInfoLabel(String text, Paint textfill) {
+ infoLabel.setText(text);
+ infoLabel.setTextFill(textfill);
+ infoLabel.setVisible(true);
+ }
+
+ // Context menu actions
+
+ @FXML
+ private void copyMessage() {
+ try {
+ Toolkit.getDefaultToolkit()
+ .getSystemClipboard()
+ .setContents(new StringSelection(messageList.getSelectionModel().getSelectedItem().getText()), null);
+ } catch (final NullPointerException e) {}
+ }
+
+ @FXML
+ private void deleteMessage() { try {} catch (final NullPointerException e) {} }
+
+ @FXML
+ private void forwardMessage() { try {} catch (final NullPointerException e) {} }
+
+ @FXML
+ private void quoteMessage() { try {} catch (final NullPointerException e) {} }
+
+ @FXML
+ private void deleteContact() { try {} catch (final NullPointerException e) {} }
+
+ @FXML
+ private void copyAndPostMessage() {
+ final var messageText = messageTextArea.getText();
+ Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(messageText), null);
+ postMessage();
+ messageTextArea.setText(messageText);
+ updateRemainingCharsLabel();
+ postButton.setDisable(messageText.isBlank());
}
}
diff --git a/src/main/java/envoy/client/ui/controller/ContactSearchScene.java b/src/main/java/envoy/client/ui/controller/ContactSearchScene.java
index f2976c9..54c505c 100644
--- a/src/main/java/envoy/client/ui/controller/ContactSearchScene.java
+++ b/src/main/java/envoy/client/ui/controller/ContactSearchScene.java
@@ -15,7 +15,7 @@ import envoy.client.ui.SceneContext;
import envoy.data.Contact;
import envoy.event.ElementOperation;
import envoy.event.EventBus;
-import envoy.event.contact.ContactOperationEvent;
+import envoy.event.contact.ContactOperation;
import envoy.event.contact.ContactSearchRequest;
import envoy.event.contact.ContactSearchResult;
import envoy.util.EnvoyLog;
@@ -57,6 +57,7 @@ public class ContactSearchScene {
/**
* @param sceneContext enables the user to return to the chat scene
+ * @param localDB the local database to which new contacts are added
* @since Envoy Client v0.1-beta
*/
public void initializeData(SceneContext sceneContext, LocalDB localDB) {
@@ -67,10 +68,8 @@ public class ContactSearchScene {
@FXML
private void initialize() {
contactList.setCellFactory(e -> new ContactListCell());
- eventBus.register(ContactSearchResult.class, response -> Platform.runLater(() -> {
- contactList.getItems().clear();
- contactList.getItems().addAll(response.get());
- }));
+ eventBus.register(ContactSearchResult.class,
+ response -> Platform.runLater(() -> { contactList.getItems().clear(); contactList.getItems().addAll(response.get()); }));
}
/**
@@ -108,20 +107,20 @@ public class ContactSearchScene {
}
/**
- * Sends an {@link ContactOperationEvent} for every selected contact to the
+ * Sends an {@link ContactOperation} for every selected contact to the
* server.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void contactListClicked() {
- final var contact = contactList.getSelectionModel().getSelectedItem();
+ final var contact = contactList.getSelectionModel().getSelectedItem();
if (contact != null) {
final var alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Add Contact to Contact List");
alert.setHeaderText("Add the user " + contact.getName() + " to your contact list?");
alert.showAndWait().filter(btn -> btn == ButtonType.OK).ifPresent(btn -> {
- final var event = new ContactOperationEvent(contact, ElementOperation.ADD);
+ final var event = new ContactOperation(contact, ElementOperation.ADD);
// Sends the event to the server
eventBus.dispatch(new SendEvent(event));
// Updates the UI
diff --git a/src/main/java/envoy/client/ui/controller/GroupCreationScene.java b/src/main/java/envoy/client/ui/controller/GroupCreationScene.java
index af91d91..c6d7d05 100644
--- a/src/main/java/envoy/client/ui/controller/GroupCreationScene.java
+++ b/src/main/java/envoy/client/ui/controller/GroupCreationScene.java
@@ -5,15 +5,16 @@ import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.*;
+import javafx.scene.control.Alert.AlertType;
import envoy.client.data.LocalDB;
import envoy.client.event.SendEvent;
import envoy.client.ui.ContactListCell;
import envoy.client.ui.SceneContext;
import envoy.data.Contact;
-import envoy.data.User;
import envoy.event.EventBus;
-import envoy.event.GroupCreationEvent;
+import envoy.event.GroupCreation;
+import envoy.util.Bounds;
/**
* Project: envoy-client
@@ -25,21 +26,18 @@ import envoy.event.GroupCreationEvent;
*/
public class GroupCreationScene {
- @FXML
- private Button backButton;
-
@FXML
private Button createButton;
@FXML
- private TextField groupNameBar;
+ private TextField groupNameField;
@FXML
private ListView contactList;
private SceneContext sceneContext;
- private static EventBus eventBus = EventBus.getInstance();
+ private static final EventBus eventBus = EventBus.getInstance();
@FXML
private void initialize() {
@@ -49,27 +47,43 @@ public class GroupCreationScene {
/**
* @param sceneContext enables the user to return to the chat scene
+ * @param localDB the local database from which potential group members can
+ * be selected
* @since Envoy Client v0.1-beta
*/
public void initializeData(SceneContext sceneContext, LocalDB localDB) {
- this.sceneContext = sceneContext;
+ this.sceneContext = sceneContext;
Platform.runLater(() -> contactList.getItems()
- .addAll(localDB.getUsers()
- .values()
- .stream()
- .filter(c -> c instanceof User && c.getID() != localDB.getUser().getID())
- .collect(Collectors.toList())));
+ .addAll(localDB.getUsers().values().stream().filter(c -> c.getID() != localDB.getUser().getID()).collect(Collectors.toList())));
}
/**
- * Sends a {@link GroupCreationEvent} to the server.
+ * Enables the {@code createButton} if at least one contact is selected.
+ *
+ * @since Envoy Client v0.1-beta
+ */
+ @FXML
+ private void contactListClicked() { createButton.setDisable(contactList.getSelectionModel().isEmpty()); }
+
+ /**
+ * Sends a {@link GroupCreation} to the server and closes this scene.
+ *
+ * If the given group name is not valid, an error is displayed instead.
*
* @since Envoy Client v0.1-beta
*/
@FXML
- private void sendGroupObject() {
- eventBus.dispatch(new SendEvent(new GroupCreationEvent(groupNameBar.getText(),
- contactList.getSelectionModel().getSelectedItems().stream().map(Contact::getID).collect(Collectors.toSet()))));
+ private void createButtonClicked() {
+ final var name = groupNameField.getText();
+ if (!Bounds.isValidContactName(name)) {
+ new Alert(AlertType.ERROR, "The entered group name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait();
+ groupNameField.clear();
+ } else {
+ eventBus.dispatch(new SendEvent(new GroupCreation(name,
+ contactList.getSelectionModel().getSelectedItems().stream().map(Contact::getID).collect(Collectors.toSet()))));
+ new Alert(AlertType.INFORMATION, String.format("Group '%s' successfully created.", name)).showAndWait();
+ sceneContext.pop();
+ }
}
@FXML
diff --git a/src/main/java/envoy/client/ui/controller/LoginScene.java b/src/main/java/envoy/client/ui/controller/LoginScene.java
index f015b6e..380842a 100644
--- a/src/main/java/envoy/client/ui/controller/LoginScene.java
+++ b/src/main/java/envoy/client/ui/controller/LoginScene.java
@@ -3,6 +3,7 @@ package envoy.client.ui.controller;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
+import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Platform;
@@ -10,19 +11,19 @@ import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType;
-import envoy.client.data.Cache;
-import envoy.client.data.ClientConfig;
-import envoy.client.data.LocalDB;
+import envoy.client.data.*;
import envoy.client.net.Client;
import envoy.client.ui.SceneContext;
+import envoy.client.ui.Startup;
import envoy.data.LoginCredentials;
import envoy.data.Message;
import envoy.data.User;
import envoy.data.User.UserStatus;
import envoy.event.EventBus;
-import envoy.event.HandshakeRejectionEvent;
-import envoy.event.MessageStatusChangeEvent;
+import envoy.event.HandshakeRejection;
+import envoy.event.MessageStatusChange;
import envoy.exception.EnvoyException;
+import envoy.util.Bounds;
import envoy.util.EnvoyLog;
/**
@@ -54,11 +55,11 @@ public final class LoginScene {
@FXML
private Label connectionLabel;
- private Client client;
- private LocalDB localDB;
- private Cache receivedMessageCache;
- private Cache receivedMessageStatusChangeEventCache;
- private SceneContext sceneContext;
+ private Client client;
+ private LocalDB localDB;
+ private Cache receivedMessageCache;
+ private Cache receivedMessageStatusChangeCache;
+ private SceneContext sceneContext;
private static final Logger logger = EnvoyLog.getLogger(LoginScene.class);
private static final EventBus eventBus = EventBus.getInstance();
@@ -69,29 +70,34 @@ public final class LoginScene {
connectionLabel.setText("Server: " + config.getServer() + ":" + config.getPort());
// Show an alert after an unsuccessful handshake
- eventBus.register(HandshakeRejectionEvent.class,
- e -> Platform.runLater(() -> { clearPasswordFields(); new Alert(AlertType.ERROR, e.get()).showAndWait(); }));
+ eventBus.register(HandshakeRejection.class, e -> Platform.runLater(() -> { new Alert(AlertType.ERROR, e.get()).showAndWait(); }));
}
/**
* Loads the login dialog using the FXML file {@code LoginDialog.fxml}.
*
- * @param client the client used to perform the handshake
- * @param localDB the local database used for offline login
- * @param receivedMessageCache the cache storing messages received during
- * the handshake
- * @param receivedMessageStatusChangeEventCache the cache storing messageStatusChangeEvents received during handshake
- * @param sceneContext the scene context used to initialize the chat
- * scene
+ * @param client the client used to perform the
+ * handshake
+ * @param localDB the local database used for offline
+ * login
+ * @param receivedMessageCache the cache storing messages received
+ * during
+ * the handshake
+ * @param receivedMessageStatusChangeCache the cache storing
+ * messageStatusChangeEvents received
+ * during handshake
+ * @param sceneContext the scene context used to initialize
+ * the chat
+ * scene
* @since Envoy Client v0.1-beta
*/
public void initializeData(Client client, LocalDB localDB, Cache receivedMessageCache,
- Cache receivedMessageStatusChangeEventCache, SceneContext sceneContext) {
- this.client = client;
- this.localDB = localDB;
- this.receivedMessageCache = receivedMessageCache;
- this.receivedMessageStatusChangeEventCache = receivedMessageStatusChangeEventCache;
- this.sceneContext = sceneContext;
+ Cache receivedMessageStatusChangeCache, SceneContext sceneContext) {
+ this.client = client;
+ this.localDB = localDB;
+ this.receivedMessageCache = receivedMessageCache;
+ this.receivedMessageStatusChangeCache = receivedMessageStatusChangeCache;
+ this.sceneContext = sceneContext;
// Prepare handshake
localDB.loadIDGenerator();
@@ -108,14 +114,19 @@ public final class LoginScene {
// Prevent registration with unequal passwords
if (registerCheckBox.isSelected() && !passwordField.getText().equals(repeatPasswordField.getText())) {
- clearPasswordFields();
new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait();
- } else performHandshake(new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), registerCheckBox.isSelected()));
+ repeatPasswordField.clear();
+ } else if (!Bounds.isValidContactName(userTextField.getText())) {
+ new Alert(AlertType.ERROR, "The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait();
+ userTextField.clear();
+ } else
+ performHandshake(
+ new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), registerCheckBox.isSelected(), Startup.VERSION));
}
@FXML
private void offlineModeButtonPressed() {
- attemptOfflineMode(new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), false));
+ attemptOfflineMode(new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), false, Startup.VERSION));
}
@FXML
@@ -124,25 +135,24 @@ public final class LoginScene {
// Make repeat password field and label visible / invisible
repeatPasswordField.setVisible(registerCheckBox.isSelected());
repeatPasswordLabel.setVisible(registerCheckBox.isSelected());
- clearPasswordFields();
}
@FXML
private void abortLogin() {
- logger.info("The login process has been cancelled. Exiting...");
+ logger.log(Level.INFO, "The login process has been cancelled. Exiting...");
System.exit(0);
}
private void performHandshake(LoginCredentials credentials) {
try {
- client.performHandshake(credentials, receivedMessageCache, receivedMessageStatusChangeEventCache);
+ client.performHandshake(credentials, receivedMessageCache, receivedMessageStatusChangeCache);
if (client.isOnline()) {
- client.initReceiver(localDB, receivedMessageCache, receivedMessageStatusChangeEventCache);
+ client.initReceiver(localDB, receivedMessageCache, receivedMessageStatusChangeCache);
loadChatScene();
}
} catch (IOException | InterruptedException | TimeoutException e) {
- logger.warning("Could not connect to server: " + e);
- logger.finer("Attempting offline mode...");
+ logger.log(Level.WARNING, "Could not connect to server: ", e);
+ logger.log(Level.FINER, "Attempting offline mode...");
attemptOfflineMode(credentials);
}
}
@@ -154,10 +164,10 @@ public final class LoginScene {
final User clientUser = (User) localDB.getUsers().get(credentials.getIdentifier());
if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown");
client.setSender(clientUser);
- new Alert(AlertType.WARNING, "A connection to the server could not be established. Starting in offline mode.").showAndWait();
loadChatScene();
} catch (final Exception e) {
new Alert(AlertType.ERROR, "Client error: " + e).showAndWait();
+ logger.log(Level.SEVERE, "Offline mode could not be loaded: ", e);
System.exit(1);
}
}
@@ -174,22 +184,24 @@ public final class LoginScene {
} catch (final FileNotFoundException e) {
// The local database file has not yet been created, probably first login
} catch (final Exception e) {
- e.printStackTrace();
new Alert(AlertType.ERROR, "Error while loading local database: " + e + "\nChats will not be stored locally.").showAndWait();
+ logger.log(Level.WARNING, "Could not load local database: ", e);
}
// Initialize write proxy
final var writeProxy = client.createWriteProxy(localDB);
- if (client.isOnline()) {
+ localDB.synchronize();
- // Save all users to the local database and flush cache
- localDB.setUsers(client.getUsers());
- localDB.createMissingChats();
- writeProxy.flushCache();
- } else
+ if (client.isOnline()) writeProxy.flushCache();
+ else
// Set all contacts to offline mode
- localDB.getUsers().values().stream().filter(User.class::isInstance).map(User.class::cast).forEach(u -> u.setStatus(UserStatus.OFFLINE));
+ localDB.getChats()
+ .stream()
+ .map(Chat::getRecipient)
+ .filter(User.class::isInstance)
+ .map(User.class::cast)
+ .forEach(u -> u.setStatus(UserStatus.OFFLINE));
// Load ChatScene
sceneContext.pop();
@@ -200,11 +212,6 @@ public final class LoginScene {
// Relay unread messages from cache
if (receivedMessageCache != null && client.isOnline()) receivedMessageCache.relay();
- if (receivedMessageStatusChangeEventCache != null && client.isOnline()) receivedMessageStatusChangeEventCache.relay();
- }
-
- private void clearPasswordFields() {
- passwordField.clear();
- repeatPasswordField.clear();
+ if (receivedMessageStatusChangeCache != null && client.isOnline()) receivedMessageStatusChangeCache.relay();
}
}
diff --git a/src/main/java/envoy/client/ui/settings/package-info.java b/src/main/java/envoy/client/ui/settings/package-info.java
index f31317e..e908b93 100644
--- a/src/main/java/envoy/client/ui/settings/package-info.java
+++ b/src/main/java/envoy/client/ui/settings/package-info.java
@@ -1,7 +1,7 @@
/**
* This package contains classes used for representing the settings
- * visually.
- *
+ * visually.
+ *