diff --git a/pom.xml b/pom.xml
index 0b5487d..ec25577 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,5 +39,30 @@
src/main/resources
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.4.1
+
+
+ package
+
+ single
+
+
+
+
+ envoy.client.ui.Startup
+
+
+
+ jar-with-dependencies
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/envoy/client/Settings.java b/src/main/java/envoy/client/Settings.java
index 02b6c88..efb03d6 100644
--- a/src/main/java/envoy/client/Settings.java
+++ b/src/main/java/envoy/client/Settings.java
@@ -6,6 +6,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.prefs.Preferences;
+import envoy.client.data.Config;
import envoy.client.ui.Color;
import envoy.client.ui.Theme;
import envoy.util.SerializationUtils;
diff --git a/src/main/java/envoy/client/data/Cache.java b/src/main/java/envoy/client/data/Cache.java
new file mode 100644
index 0000000..80ef26f
--- /dev/null
+++ b/src/main/java/envoy/client/data/Cache.java
@@ -0,0 +1,61 @@
+package envoy.client.data;
+
+import java.io.Serializable;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.logging.Logger;
+
+import envoy.client.util.EnvoyLog;
+
+/**
+ * Stores elements in a queue to process them later.
+ *
+ * Project: envoy-client
+ * File: Cache.java
+ * Created: 6 Feb 2020
+ *
+ * @param the type of cached elements
+ * @author Kai S. K. Engelbart
+ * @since Envoy v0.3-alpha
+ */
+public class Cache implements Consumer, Serializable {
+
+ private final Queue elements = new LinkedList<>();
+ private transient Consumer processor;
+
+ private static final Logger logger = EnvoyLog.getLogger(Cache.class.getSimpleName());
+ private static final long serialVersionUID = 7343544675545545076L;
+
+ /**
+ * Adds an element to the cache.
+ *
+ * @param element the element to add
+ * @since Envoy v0.3-alpha
+ */
+ @Override
+ public void accept(T element) {
+ logger.info(String.format("Adding element %s to cache", element));
+ elements.offer(element);
+ }
+
+ /**
+ * Sets the processor to which cached elements are relayed.
+ *
+ * @param processor the processor to set
+ * @since Envoy v0.3-alpha
+ */
+ public void setProcessor(Consumer processor) { this.processor = processor; }
+
+ /**
+ * Relays all cached elements to the processor.
+ *
+ * @throws IllegalStateException if the processor is not initialized
+ * @since Envoy v0.3-alpha
+ */
+ public void relay() {
+ if (processor == null) throw new IllegalStateException("Processor is not defined");
+ elements.forEach(processor::accept);
+ elements.clear();
+ }
+}
diff --git a/src/main/java/envoy/client/data/Chat.java b/src/main/java/envoy/client/data/Chat.java
index f10e816..49083a7 100644
--- a/src/main/java/envoy/client/data/Chat.java
+++ b/src/main/java/envoy/client/data/Chat.java
@@ -3,7 +3,7 @@ package envoy.client.data;
import java.io.IOException;
import java.io.Serializable;
-import envoy.client.net.Client;
+import envoy.client.net.WriteProxy;
import envoy.client.ui.list.ComponentListModel;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
@@ -52,22 +52,20 @@ public class Chat implements Serializable {
* {@code READ} starting from the bottom and stopping once a read message is
* found.
*
- * @param client the client instance used to notify the server about the message
- * status changes
+ * @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
* delivered to the server
* @since Envoy v0.3-alpha
*/
- public void read(Client client) throws IOException {
+ public void read(WriteProxy writeProxy) throws IOException {
for (int i = model.size() - 1; i >= 0; --i) {
final Message m = model.get(i);
if (m.getSenderId() == recipient.getId()) {
if (m.getStatus() == MessageStatus.READ) break;
else {
m.setStatus(MessageStatus.READ);
-
- // TODO: Cache events in offline mode
- client.sendEvent(new MessageStatusChangeEvent(m));
+ writeProxy.writeMessageStatusChangeEvent(new MessageStatusChangeEvent(m));
}
}
}
diff --git a/src/main/java/envoy/client/Config.java b/src/main/java/envoy/client/data/Config.java
similarity index 69%
rename from src/main/java/envoy/client/Config.java
rename to src/main/java/envoy/client/data/Config.java
index 24e7780..1f351ea 100644
--- a/src/main/java/envoy/client/Config.java
+++ b/src/main/java/envoy/client/data/Config.java
@@ -1,9 +1,12 @@
-package envoy.client;
+package envoy.client.data;
import java.io.File;
+import java.security.NoSuchAlgorithmException;
import java.util.*;
+import java.util.function.Function;
import java.util.logging.Level;
+import envoy.data.LoginCredentials;
import envoy.exception.EnvoyException;
/**
@@ -26,14 +29,15 @@ public class Config {
private static Config config;
private Config() {
- items.put("server", new ConfigItem<>("server", "s", (input) -> input, null));
- items.put("port", new ConfigItem<>("port", "p", (input) -> Integer.parseInt(input), null));
- items.put("localDB", new ConfigItem<>("localDB", "db", (input) -> new File(input), new File("localDB")));
- items.put("ignoreLocalDB", new ConfigItem<>("ignoreLocalDB", "nodb", (input) -> Boolean.parseBoolean(input), false));
- items.put("homeDirectory",
- new ConfigItem<>("homeDirectory", "h", (input) -> new File(input), new File(System.getProperty("user.home"), ".envoy")));
- items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", (input) -> Level.parse(input), Level.CONFIG));
- items.put("consoleLevelBarrier", new ConfigItem<>("consoleLevelBarrier", "cb", (input) -> Level.parse(input), Level.FINEST));
+ items.put("server", new ConfigItem<>("server", "s", Function.identity(), null, true));
+ items.put("port", new ConfigItem<>("port", "p", Integer::parseInt, null, true));
+ items.put("localDB", new ConfigItem<>("localDB", "db", File::new, new File("localDB"), true));
+ items.put("ignoreLocalDB", new ConfigItem<>("ignoreLocalDB", "nodb", Boolean::parseBoolean, false, false));
+ items.put("homeDirectory", new ConfigItem<>("homeDirectory", "h", File::new, new File(System.getProperty("user.home"), ".envoy"), true));
+ items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", Level::parse, Level.CONFIG, true));
+ items.put("consoleLevelBarrier", new ConfigItem<>("consoleLevelBarrier", "cb", Level::parse, Level.FINEST, true));
+ items.put("user", new ConfigItem<>("user", "u", Function.identity()));
+ items.put("password", new ConfigItem<>("password", "pw", String::toCharArray));
}
/**
@@ -95,10 +99,12 @@ public class Config {
}
/**
- * @return {@code true} if server, port and localDB directory are known.
+ * @return {@code true} if all mandatory config items are initialized
* @since Envoy v0.1-alpha
*/
- public boolean isInitialized() { return items.values().stream().map(ConfigItem::get).noneMatch(Objects::isNull); }
+ public boolean isInitialized() {
+ return items.values().stream().filter(ConfigItem::isMandatory).map(ConfigItem::get).noneMatch(Objects::isNull);
+ }
/**
* @return the host name of the Envoy server
@@ -141,4 +147,35 @@ public class Config {
* @since Envoy v0.2-alpha
*/
public Level getConsoleLevelBarrier() { return (Level) items.get("consoleLevelBarrier").get(); }
+
+ /**
+ * @return the user name
+ * @since Envoy v0.3-alpha
+ */
+ public String getUser() { return (String) items.get("user").get(); }
+
+ /**
+ * @return the password
+ * @since Envoy v0.3-alpha
+ */
+ public char[] getPassword() { return (char[]) items.get("password").get(); }
+
+ /**
+ * @return {@code true} if user name and password are set
+ * @since Envoy v0.3-alpha
+ */
+ public boolean hasLoginCredentials() { return getUser() != null && getPassword() != null; }
+
+ /**
+ * @return login credentials for the specified user name and password, without
+ * the registration option
+ * @since Envoy v0.3-alpha
+ */
+ public LoginCredentials getLoginCredentials() {
+ try {
+ return new LoginCredentials(getUser(), getPassword(), false);
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/main/java/envoy/client/ConfigItem.java b/src/main/java/envoy/client/data/ConfigItem.java
similarity index 53%
rename from src/main/java/envoy/client/ConfigItem.java
rename to src/main/java/envoy/client/data/ConfigItem.java
index 87e927b..6a987cb 100644
--- a/src/main/java/envoy/client/ConfigItem.java
+++ b/src/main/java/envoy/client/data/ConfigItem.java
@@ -1,4 +1,4 @@
-package envoy.client;
+package envoy.client.data;
import java.util.function.Function;
@@ -9,49 +9,70 @@ import java.util.function.Function;
* Project: envoy-clientChess
* File: ConfigItem.javaEvent.java
* Created: 21.12.2019
- *
+ *
* @author Kai S. K. Engelbart
* @param the type of the config item's value
* @since Envoy v0.2-alpha
*/
public class ConfigItem {
- private String commandLong, commandShort;
- private Function parseFunction;
- private T value;
+ private final String commandLong, commandShort;
+ private final Function parseFunction;
+ private final boolean mandatory;
+
+ private T value;
/**
- * Initializes a {@link ConfigItem}
- *
+ * Initializes a {@link ConfigItem}.
+ *
* @param commandLong the long command line argument to set this value
* @param commandShort the short command line argument to set this value
* @param parseFunction the {@code Function} that parses the value
* from a string
* @param defaultValue the optional default value to set before parsing
+ * @param mandatory indicated that this config item must be initialized with
+ * a non-null value
* @since Envoy v0.2-alpha
*/
- public ConfigItem(String commandLong, String commandShort, Function parseFunction, T defaultValue) {
+ public ConfigItem(String commandLong, String commandShort, Function parseFunction, T defaultValue, boolean mandatory) {
this.commandLong = commandLong;
this.commandShort = commandShort;
this.parseFunction = parseFunction;
+ this.mandatory = mandatory;
value = defaultValue;
}
/**
- * Parses this {@ConfigItem}'s value from a string
+ * Initializes an optional {@link ConfigItem} without a default value.
+ *
+ * @param commandLong the long command line argument to set this value
+ * @param commandShort the short command line argument to set this value
+ * @param parseFunction the {@code Function} that parses the value
+ * from a string
+ * @since Envoy v0.3-alpha
+ */
+ public ConfigItem(String commandLong, String commandShort, Function parseFunction) {
+ this(commandLong, commandShort, parseFunction, null, false);
+ }
+
+ /**
+ * Parses this {@ConfigItem}'s value from a string.
+ *
* @param input the string to parse from
* @since Envoy v0.2-alpha
*/
public void parse(String input) { value = parseFunction.apply(input); }
/**
- * @return The long command line argument to set the value of this {@link ConfigItem}
+ * @return The long command line argument to set the value of this
+ * {@link ConfigItem}
* @since Envoy v0.2-alpha
*/
public String getCommandLong() { return commandLong; }
/**
- * @return The short command line argument to set the value of this {@link ConfigItem}
+ * @return The short command line argument to set the value of this
+ * {@link ConfigItem}
* @since Envoy v0.2-alpha
*/
public String getCommandShort() { return commandShort; }
@@ -61,4 +82,11 @@ public class ConfigItem {
* @since Envoy v0.2-alpha
*/
public T get() { return value; }
+
+ /**
+ * @return {@code true} if this {@link ConfigItem} is mandatory for successful
+ * application initialization
+ * @since Envoy v0.3-alpha
+ */
+ public boolean isMandatory() { return mandatory; }
}
diff --git a/src/main/java/envoy/client/data/LocalDb.java b/src/main/java/envoy/client/data/LocalDb.java
index 79368a8..8034e2a 100644
--- a/src/main/java/envoy/client/data/LocalDb.java
+++ b/src/main/java/envoy/client/data/LocalDb.java
@@ -3,7 +3,9 @@ package envoy.client.data;
import java.util.*;
import envoy.data.IdGenerator;
+import envoy.data.Message;
import envoy.data.User;
+import envoy.event.MessageStatusChangeEvent;
/**
* Stores information about the current {@link User} and their {@link Chat}s.
@@ -18,10 +20,12 @@ import envoy.data.User;
*/
public abstract class LocalDb {
- protected User user;
- protected Map users = new HashMap<>();
- protected List chats = new ArrayList<>();
- protected IdGenerator idGenerator;
+ 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.
@@ -48,12 +52,12 @@ public abstract class LocalDb {
public void loadUsers() throws Exception {}
/**
- * Loads all chat data of the client user.
+ * Loads all data of the client user.
*
* @throws Exception if the loading process failed
* @since Envoy v0.3-alpha
*/
- public void loadChats() throws Exception {}
+ public void loadUserData() throws Exception {}
/**
* Loads the ID generator. Any exception thrown during this process is ignored.
@@ -115,4 +119,28 @@ public abstract class LocalDb {
* @since Envoy v0.3-alpha
*/
public boolean hasIdGenerator() { return idGenerator != null; }
+
+ /**
+ * @return the offline message cache
+ * @since Envoy v0.3-alpha
+ */
+ public Cache getMessageCache() { return messageCache; }
+
+ /**
+ * @param messageCache the offline message cache to set
+ * @since Envoy v0.3-alpha
+ */
+ public void setMessageCache(Cache messageCache) { this.messageCache = messageCache; }
+
+ /**
+ * @return the offline status cache
+ * @since Envoy v0.3-alpha
+ */
+ public Cache getStatusCache() { return statusCache; }
+
+ /**
+ * @param statusCache the offline status cache to set
+ * @since Envoy v0.3-alpha
+ */
+ public void setStatusCache(Cache statusCache) { this.statusCache = statusCache; }
}
diff --git a/src/main/java/envoy/client/data/PersistentLocalDb.java b/src/main/java/envoy/client/data/PersistentLocalDb.java
index 7856517..0a04729 100644
--- a/src/main/java/envoy/client/data/PersistentLocalDb.java
+++ b/src/main/java/envoy/client/data/PersistentLocalDb.java
@@ -5,7 +5,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
-import envoy.client.ConfigItem;
import envoy.data.IdGenerator;
import envoy.util.SerializationUtils;
@@ -23,7 +22,7 @@ import envoy.util.SerializationUtils;
*/
public class PersistentLocalDb extends LocalDb {
- private File localDBDir, localDBFile, usersFile, idGeneratorFile;
+ private File localDBDir, localDBFile, usersFile, idGeneratorFile, messageCacheFile, statusCacheFile;
/**
* Initializes an empty local database without a directory. All changes made to
@@ -40,18 +39,18 @@ public class PersistentLocalDb extends LocalDb {
* 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
+ * @param localDbDir the directory in which to store users and chats
* @throws IOException if the PersistentLocalDb could not be initialized
* @since Envoy v0.1-alpha
*/
- public PersistentLocalDb(File localDBDir) throws IOException {
- this.localDBDir = localDBDir;
+ public PersistentLocalDb(File localDbDir) throws IOException {
+ localDBDir = localDbDir;
// 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");
+ 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");
}
/**
@@ -63,7 +62,9 @@ public class PersistentLocalDb extends LocalDb {
@Override
public void initializeUserStorage() {
if (user == null) throw new NullPointerException("Client user is null");
- localDBFile = new File(localDBDir, user.getId() + ".db");
+ 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");
}
@Override
@@ -71,8 +72,12 @@ public class PersistentLocalDb extends LocalDb {
// Save users
SerializationUtils.write(usersFile, users);
- // Save chats
- if (user != null) SerializationUtils.write(localDBFile, chats);
+ // Save user data
+ if (user != null) {
+ SerializationUtils.write(localDBFile, chats);
+ SerializationUtils.write(messageCacheFile, messageCache);
+ SerializationUtils.write(statusCacheFile, statusCache);
+ }
// Save id generator
if (hasIdGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
@@ -82,7 +87,11 @@ public class PersistentLocalDb extends LocalDb {
public void loadUsers() throws ClassNotFoundException, IOException { users = SerializationUtils.read(usersFile, HashMap.class); }
@Override
- public void loadChats() throws ClassNotFoundException, IOException { chats = SerializationUtils.read(localDBFile, ArrayList.class); }
+ public void loadUserData() throws ClassNotFoundException, IOException {
+ chats = SerializationUtils.read(localDBFile, ArrayList.class);
+ messageCache = SerializationUtils.read(messageCacheFile, Cache.class);
+ statusCache = SerializationUtils.read(statusCacheFile, Cache.class);
+ }
@Override
public void loadIdGenerator() {
diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java
index c99ef13..fec5570 100644
--- a/src/main/java/envoy/client/net/Client.java
+++ b/src/main/java/envoy/client/net/Client.java
@@ -9,7 +9,8 @@ import java.util.logging.Logger;
import javax.naming.TimeLimitExceededException;
-import envoy.client.Config;
+import envoy.client.data.Cache;
+import envoy.client.data.Config;
import envoy.client.data.LocalDb;
import envoy.client.event.SearchResultEvent;
import envoy.client.util.EnvoyLog;
@@ -51,16 +52,17 @@ 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 localDb the local database used to persist the current
- * {@link IdGenerator}
- * @return a message cache containing all unread messages from the server that
- * can be relayed after initialization
+ * @param credentials the login credentials of the user
+ * @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
* @throws Exception if the online mode could not be entered or the request
* failed for some other reason
* @since Envoy v0.2-alpha
*/
- public MessageCache onlineInit(LoginCredentials credentials, LocalDb localDb) throws Exception {
+ public void onlineInit(LoginCredentials credentials, LocalDb localDb, Cache receivedMessageCache) throws Exception {
// Establish TCP connection
logger.info(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
socket = new Socket(config.getServer(), config.getPort());
@@ -69,13 +71,10 @@ public class Client implements Closeable {
// Create message receiver
receiver = new Receiver(socket.getInputStream());
- // Create cache for unread messages
- final MessageCache cache = new MessageCache();
-
// Register user creation processor, contact list processor and message cache
receiver.registerProcessor(User.class, sender -> { logger.info("Acquired user object " + sender); this.sender = sender; });
receiver.registerProcessor(Contacts.class, contacts -> { logger.info("Acquired contacts object " + contacts); this.contacts = contacts; });
- receiver.registerProcessor(Message.class, cache);
+ receiver.registerProcessor(Message.class, receivedMessageCache);
// Start receiver
new Thread(receiver).start();
@@ -98,15 +97,20 @@ public class Client implements Closeable {
receiver.removeAllProcessors();
// Register processors for message and status handling
+
+ // Process incoming messages
final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor();
receiver.registerProcessor(Message.class, receivedMessageProcessor);
// Relay cached unread messages
- cache.setProcessor(receivedMessageProcessor);
+ receivedMessageCache.setProcessor(receivedMessageProcessor);
// Process message status changes
receiver.registerProcessor(MessageStatusChangeEvent.class, new MessageStatusChangeEventProcessor());
+ // Process user status changes
+ receiver.registerProcessor(UserStatusChangeEvent.class, new UserStatusChangeProcessor(this));
+
// Process message ID generation
receiver.registerProcessor(IdGenerator.class, localDb::setIdGenerator);
@@ -115,10 +119,19 @@ public class Client implements Closeable {
// Request a generator if none is present or the existing one is consumed
if (!localDb.hasIdGenerator() || !localDb.getIdGenerator().hasNext()) requestIdGenerator();
-
- return cache;
}
+ /**
+ * Creates a new write proxy that uses this client to communicate with the
+ * server.
+ *
+ * @param localDb the local database that the write proxy will use to access
+ * caches
+ * @return a new write proxy
+ * @since Envoy Client v0.3-alpha
+ */
+ public WriteProxy createWriteProxy(LocalDb localDb) { return new WriteProxy(this, localDb); }
+
/**
* Sends a message to the server. The message's status will be incremented once
* it was delivered successfully.
@@ -197,4 +210,16 @@ public class Client implements Closeable {
* @since Envoy v0.2-alpha
*/
public boolean isOnline() { return online; }
-}
\ No newline at end of file
+
+ /**
+ * @return the contacts of this {@link Client}
+ * @since Envoy v0.3-alpha
+ */
+ public Contacts getContacts() { return contacts; }
+
+ /**
+ * @param contacts the contacts to set
+ * @since Envoy v0.3-alpha
+ */
+ public void setContacts(Contacts contacts) { this.contacts = contacts; }
+}
diff --git a/src/main/java/envoy/client/net/MessageCache.java b/src/main/java/envoy/client/net/MessageCache.java
deleted file mode 100644
index 1fc49c3..0000000
--- a/src/main/java/envoy/client/net/MessageCache.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package envoy.client.net;
-
-import java.util.LinkedList;
-import java.util.Queue;
-import java.util.function.Consumer;
-import java.util.logging.Logger;
-
-import envoy.client.util.EnvoyLog;
-import envoy.data.Message;
-
-/**
- * Stores messages in a queue until the application initialization is complete.
- * The messages can then be relayed to a processor.
- *
- * Project: envoy-client
- * File: MessageCache.java
- * Created: 4 Feb 2020
- *
- * @author Kai S. K. Engelbart
- * @since Envoy v0.3-alpha
- */
-public class MessageCache implements Consumer {
-
- private final Queue messages = new LinkedList<>();
- private Consumer processor;
-
- private static final Logger logger = EnvoyLog.getLogger(MessageCache.class.getSimpleName());
-
- /**
- * Adds a message to the cache.
- *
- * @since Envoy v0.3-alpha
- */
- @Override
- public void accept(Message message) {
- logger.info(String.format("Adding message %s to cache", message));
- messages.add(message);
- }
-
- /**
- * Sets the processor to which messages are relayed.
- *
- * @param processor the processor to set
- * @since Envoy v0.3-alpha
- */
- public void setProcessor(Consumer processor) { this.processor = processor; }
-
- /**
- * Relays all cached messages to the processor.
- *
- * @since Envoy v0.3-alpha
- */
- public void relayMessages() { messages.forEach(processor::accept); }
-}
diff --git a/src/main/java/envoy/client/net/UserStatusChangeProcessor.java b/src/main/java/envoy/client/net/UserStatusChangeProcessor.java
new file mode 100644
index 0000000..744e6e8
--- /dev/null
+++ b/src/main/java/envoy/client/net/UserStatusChangeProcessor.java
@@ -0,0 +1,37 @@
+package envoy.client.net;
+
+import java.util.function.Consumer;
+import java.util.logging.Logger;
+
+import envoy.client.util.EnvoyLog;
+import envoy.event.EventBus;
+import envoy.event.UserStatusChangeEvent;
+
+/**
+ * Project: envoy-client
+ * File: UserStatusChangeProcessor.java
+ * Created: 2 Feb 2020
+ *
+ * @author Leon Hofmeister
+ * @since Envoy v0.3-alpha
+ */
+public class UserStatusChangeProcessor implements Consumer {
+
+ private Client client;
+
+ private static final Logger logger = EnvoyLog.getLogger(UserStatusChangeProcessor.class.getSimpleName());
+
+ /**
+ * @param client the {@link Client} who receives an
+ * {@link UserStatusChangeEvent}
+ * @since Envoy v0.3-alpha
+ */
+ public UserStatusChangeProcessor(Client client) { this.client = client; }
+
+ @Override
+ public void accept(UserStatusChangeEvent evt) {
+ logger.info("Received " + evt);
+ client.getContacts().getContacts().stream().filter((user) -> user.getId() == evt.getId()).findFirst().get().setStatus(evt.get());
+ EventBus.getInstance().dispatch(evt);
+ }
+}
diff --git a/src/main/java/envoy/client/net/WriteProxy.java b/src/main/java/envoy/client/net/WriteProxy.java
new file mode 100644
index 0000000..98206ae
--- /dev/null
+++ b/src/main/java/envoy/client/net/WriteProxy.java
@@ -0,0 +1,103 @@
+package envoy.client.net;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import envoy.client.data.LocalDb;
+import envoy.client.util.EnvoyLog;
+import envoy.data.Message;
+import envoy.event.MessageStatusChangeEvent;
+
+/**
+ * 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.
+ *
+ * Project: envoy-client
+ * File: WriteProxy.java
+ * Created: 6 Feb 2020
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy v0.3-alpha
+ */
+public class WriteProxy {
+
+ private final Client client;
+ private final LocalDb localDb;
+
+ private static final Logger logger = EnvoyLog.getLogger(WriteProxy.class.getSimpleName());
+
+ /**
+ * Initializes a write proxy using a client and a local database. The
+ * corresponding cache processors are injected into the caches.
+ *
+ * @param client the client used to send messages and message status change
+ * events
+ * @param localDb the local database used to cache messages and message status
+ * change events
+ * @since Envoy v0.3-alpha
+ */
+ public WriteProxy(Client client, LocalDb localDb) {
+ this.client = client;
+ this.localDb = localDb;
+
+ // Initialize cache processors for messages and message status change events
+ localDb.getMessageCache().setProcessor(msg -> {
+ try {
+ client.sendMessage(msg);
+ } catch (IOException e) {
+ logger.log(Level.SEVERE, "Could not send cached message", e);
+ }
+ });
+ localDb.getStatusCache().setProcessor(evt -> {
+ try {
+ client.sendEvent(evt);
+ } catch (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
+ * server.
+ *
+ * @since Envoy v0.3-alpha
+ */
+ public void flushCache() {
+ logger.info("Sending cached messages and message status change events...");
+
+ // Send messages
+ localDb.getMessageCache().relay();
+
+ // Send message status change events
+ localDb.getStatusCache().relay();
+ }
+
+ /**
+ * Delivers a message to the server if online. Otherwise the message is cached
+ * inside the local database.
+ *
+ * @param message the message to send
+ * @throws IOException if the message could not be sent
+ * @since Envoy v0.3-alpha
+ */
+ public void writeMessage(Message message) throws IOException {
+ if (client.isOnline()) client.sendMessage(message);
+ else localDb.getMessageCache().accept(message);
+ }
+
+ /**
+ * Delivers a message status change event to the server if online. Otherwise the
+ * event is cached inside the local database.
+ *
+ * @param evt the event to send
+ * @throws IOException if the event could not be sent
+ * @since Envoy v0.3-alpha
+ */
+ public void writeMessageStatusChangeEvent(MessageStatusChangeEvent evt) throws IOException {
+ if (client.isOnline()) client.sendEvent(evt);
+ else localDb.getStatusCache().accept(evt);
+ }
+}
diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java
index c563144..f5400a3 100644
--- a/src/main/java/envoy/client/ui/ChatWindow.java
+++ b/src/main/java/envoy/client/ui/ChatWindow.java
@@ -17,6 +17,7 @@ import envoy.client.data.Chat;
import envoy.client.data.LocalDb;
import envoy.client.event.*;
import envoy.client.net.Client;
+import envoy.client.net.WriteProxy;
import envoy.client.ui.list.ComponentList;
import envoy.client.ui.list.ComponentListModel;
import envoy.client.ui.settings.SettingsScreen;
@@ -41,8 +42,9 @@ import envoy.event.ContactOperationEvent.Operation;
public class ChatWindow extends JFrame {
// User specific objects
- private Client client;
- private LocalDb localDb;
+ private Client client;
+ private WriteProxy writeProxy;
+ private LocalDb localDb;
// GUI components
private JPanel contentPane = new JPanel();
@@ -359,6 +361,9 @@ public class ChatWindow extends JFrame {
// Listen to theme changes
EventBus.getInstance().register(ThemeChangeEvent.class, (evt) -> applyTheme((Theme) evt.get()));
+ // Listen to user status changes
+ EventBus.getInstance().register(UserStatusChangeEvent.class, (evt) -> { userList.revalidate(); userList.repaint(); });
+
// Listen to received messages
EventBus.getInstance().register(MessageCreationEvent.class, (evt) -> {
Message message = ((MessageCreationEvent) evt).get();
@@ -498,8 +503,7 @@ public class ChatWindow extends JFrame {
.build();
// Send message
- // TODO: Store offline messages
- client.sendMessage(message);
+ writeProxy.writeMessage(message);
// Add message to PersistentLocalDb and update UI
currentChat.appendMessage(message);
@@ -544,7 +548,7 @@ public class ChatWindow extends JFrame {
private void readCurrentChat() {
try {
- currentChat.read(client);
+ currentChat.read(writeProxy);
messageList.synchronizeModel();
} catch (IOException e) {
e.printStackTrace();
@@ -571,24 +575,23 @@ public class ChatWindow extends JFrame {
}
/**
- * Sets the {@link Client} used by this {@link ChatWindow}.
+ * Initializes the components responsible server communication and
+ * persistence.
+ *
+ * This will trigger the display of the contact list.
*
- * @param client the {@link Client} used to send and receive messages
- * @since Envoy v0.2-alpha
+ * @param client the client used to send and receive messages
+ * @param localDb the local database used to manage stored messages
+ * and users
+ * @param writeProxy the write proxy used to send messages and status change
+ * events to the server or cache them inside the local
+ * database
+ * @since Envoy v0.3-alpha
*/
- public void setClient(Client client) { this.client = client; }
-
- /**
- * Sets the {@link LocalDb} used by this {@link ChatWindow}. After
- * invoking this
- * method, users and chats will be loaded from the database into the GUI.
- *
- * @param localDb the {@link LocalDb} used to manage stored messages
- * and users
- * @since Envoy v0.2-alpha
- */
- public void setLocalDB(LocalDb localDb) {
- this.localDb = localDb;
+ public void initContent(Client client, LocalDb localDb, WriteProxy writeProxy) {
+ this.client = client;
+ this.localDb = localDb;
+ this.writeProxy = writeProxy;
loadUsersAndChats();
}
}
diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java
index 9f308dc..a976824 100644
--- a/src/main/java/envoy/client/ui/Startup.java
+++ b/src/main/java/envoy/client/ui/Startup.java
@@ -11,15 +11,13 @@ import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
-import envoy.client.Config;
import envoy.client.Settings;
-import envoy.client.data.LocalDb;
-import envoy.client.data.PersistentLocalDb;
-import envoy.client.data.TransientLocalDb;
+import envoy.client.data.*;
import envoy.client.net.Client;
-import envoy.client.net.MessageCache;
+import envoy.client.net.WriteProxy;
import envoy.client.util.EnvoyLog;
import envoy.data.LoginCredentials;
+import envoy.data.Message;
import envoy.data.User;
import envoy.exception.EnvoyException;
@@ -64,7 +62,7 @@ public class Startup {
// Override configuration values with command line arguments
if (args.length > 0) config.load(args);
- // Check if all configuration values have been initialized
+ // Check if all mandatory configuration values have been initialized
if (!config.isInitialized()) throw new EnvoyException("Server or port are not defined");
} catch (Exception e) {
JOptionPane
@@ -77,8 +75,8 @@ public class Startup {
EnvoyLog.setFileLevelBarrier(config.getFileLevelBarrier());
EnvoyLog.setConsoleLevelBarrier(config.getConsoleLevelBarrier());
- // Ask the user for their user name and password
- LoginCredentials credentials = new LoginDialog().getCredentials();
+ // Acquire login credentials
+ LoginCredentials credentials = config.hasLoginCredentials() ? config.getLoginCredentials() : new LoginDialog().getCredentials();
if (credentials == null) {
logger.info("The login process has been aborted by the user. Exiting...");
@@ -108,11 +106,11 @@ public class Startup {
// Acquire the client user (with ID) either from the server or from the local
// database, which triggers offline mode
Client client = new Client();
- MessageCache cache = null;
+ Cache cache = new Cache<>();
try {
// Try entering online mode first
localDb.loadIdGenerator();
- cache = client.onlineInit(credentials, localDb);
+ client.onlineInit(credentials, localDb, cache);
} catch (Exception e1) {
logger.warning("Could not connect to server. Trying offline mode...");
e1.printStackTrace();
@@ -139,7 +137,7 @@ public class Startup {
// Initialize chats in local database
try {
localDb.initializeUserStorage();
- localDb.loadChats();
+ localDb.loadUserData();
} catch (FileNotFoundException e) {
// The local database file has not yet been created, probably first login
} catch (Exception e) {
@@ -150,14 +148,22 @@ public class Startup {
JOptionPane.WARNING_MESSAGE);
}
- // Save all users to the local database
- if (client.isOnline()) localDb.setUsers(client.getUsers());
+ // Initialize write proxy
+ final WriteProxy writeProxy = client.createWriteProxy(localDb);
+
+ // Save all users to the local database and flush cache
+ if (client.isOnline()) {
+ localDb.setUsers(client.getUsers());
+ writeProxy.flushCache();
+ }
// Display ChatWindow and StatusTrayIcon
EventQueue.invokeLater(() -> {
try {
- chatWindow.setClient(client);
- chatWindow.setLocalDB(localDb);
+ chatWindow.initContent(client, localDb, writeProxy);
+
+ // Relay unread messages from cache
+ if (cache != null) cache.relay();
try {
new StatusTrayIcon(chatWindow).show();
@@ -177,9 +183,6 @@ public class Startup {
}
});
- // Relay unread messages from cache
- if (cache != null) cache.relayMessages();
-
// Save Settings and PersistentLocalDb on shutdown
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
diff --git a/src/main/java/envoy/client/util/EnvoyLog.java b/src/main/java/envoy/client/util/EnvoyLog.java
index b59b714..ae6c6e5 100644
--- a/src/main/java/envoy/client/util/EnvoyLog.java
+++ b/src/main/java/envoy/client/util/EnvoyLog.java
@@ -4,7 +4,7 @@ import java.io.File;
import java.io.IOException;
import java.util.logging.*;
-import envoy.client.Config;
+import envoy.client.data.Config;
/**
* Project: envoy-client