From 76a9e2c04312e9bd31fb8355f412bda14747a70d Mon Sep 17 00:00:00 2001 From: delvh Date: Sun, 2 Feb 2020 13:44:10 +0100 Subject: [PATCH 01/11] Moved Receiver + ReceivedMessageProcessor in the net package additionally added serialVersionUIDs for the Event classes --- src/main/java/envoy/client/Client.java | 2 ++ src/main/java/envoy/client/event/MessageCreationEvent.java | 2 ++ src/main/java/envoy/client/event/MessageEvent.java | 2 ++ src/main/java/envoy/client/event/MessageModificationEvent.java | 2 ++ src/main/java/envoy/client/event/ThemeChangeEvent.java | 3 ++- .../java/envoy/client/{ => net}/ReceivedMessageProcessor.java | 2 +- src/main/java/envoy/client/{ => net}/Receiver.java | 2 +- 7 files changed, 12 insertions(+), 3 deletions(-) rename src/main/java/envoy/client/{ => net}/ReceivedMessageProcessor.java (97%) rename src/main/java/envoy/client/{ => net}/Receiver.java (99%) diff --git a/src/main/java/envoy/client/Client.java b/src/main/java/envoy/client/Client.java index 935669f..8ed3bb4 100644 --- a/src/main/java/envoy/client/Client.java +++ b/src/main/java/envoy/client/Client.java @@ -9,6 +9,8 @@ import java.util.logging.Logger; import javax.naming.TimeLimitExceededException; +import envoy.client.net.ReceivedMessageProcessor; +import envoy.client.net.Receiver; import envoy.client.util.EnvoyLog; import envoy.data.*; import envoy.event.IdGeneratorRequest; diff --git a/src/main/java/envoy/client/event/MessageCreationEvent.java b/src/main/java/envoy/client/event/MessageCreationEvent.java index 36397d9..301603e 100644 --- a/src/main/java/envoy/client/event/MessageCreationEvent.java +++ b/src/main/java/envoy/client/event/MessageCreationEvent.java @@ -11,6 +11,8 @@ import envoy.data.Message; */ public class MessageCreationEvent extends MessageEvent { + private static final long serialVersionUID = -6451021678064566774L; + /** * @param message the {@link Message} that has been created */ diff --git a/src/main/java/envoy/client/event/MessageEvent.java b/src/main/java/envoy/client/event/MessageEvent.java index a1b5c68..8bd05d1 100644 --- a/src/main/java/envoy/client/event/MessageEvent.java +++ b/src/main/java/envoy/client/event/MessageEvent.java @@ -12,6 +12,8 @@ import envoy.event.Event; */ public class MessageEvent implements Event { + private static final long serialVersionUID = 7658989461923112804L; + /** * the {@link Message} attached to this {@link MessageEvent}. */ diff --git a/src/main/java/envoy/client/event/MessageModificationEvent.java b/src/main/java/envoy/client/event/MessageModificationEvent.java index 248c6f1..3a5549a 100644 --- a/src/main/java/envoy/client/event/MessageModificationEvent.java +++ b/src/main/java/envoy/client/event/MessageModificationEvent.java @@ -11,6 +11,8 @@ import envoy.data.Message; */ public class MessageModificationEvent extends MessageEvent { + private static final long serialVersionUID = 4650039506439563116L; + /** * @param message the {@link Message} that has been modified */ diff --git a/src/main/java/envoy/client/event/ThemeChangeEvent.java b/src/main/java/envoy/client/event/ThemeChangeEvent.java index adb9707..02a15df 100644 --- a/src/main/java/envoy/client/event/ThemeChangeEvent.java +++ b/src/main/java/envoy/client/event/ThemeChangeEvent.java @@ -13,7 +13,8 @@ import envoy.event.Event; */ public class ThemeChangeEvent implements Event { - private final Theme theme; + private static final long serialVersionUID = 6756772448803774547L; + private final Theme theme; /** * Initializes a {@link ThemeChangeEvent} conveying information about the change diff --git a/src/main/java/envoy/client/ReceivedMessageProcessor.java b/src/main/java/envoy/client/net/ReceivedMessageProcessor.java similarity index 97% rename from src/main/java/envoy/client/ReceivedMessageProcessor.java rename to src/main/java/envoy/client/net/ReceivedMessageProcessor.java index d408993..27b505e 100644 --- a/src/main/java/envoy/client/ReceivedMessageProcessor.java +++ b/src/main/java/envoy/client/net/ReceivedMessageProcessor.java @@ -1,4 +1,4 @@ -package envoy.client; +package envoy.client.net; import java.util.function.Consumer; import java.util.logging.Logger; diff --git a/src/main/java/envoy/client/Receiver.java b/src/main/java/envoy/client/net/Receiver.java similarity index 99% rename from src/main/java/envoy/client/Receiver.java rename to src/main/java/envoy/client/net/Receiver.java index e6cd3c5..5030241 100644 --- a/src/main/java/envoy/client/Receiver.java +++ b/src/main/java/envoy/client/net/Receiver.java @@ -1,4 +1,4 @@ -package envoy.client; +package envoy.client.net; import java.io.ByteArrayInputStream; import java.io.InputStream; From 63990f6d5700ea21e4e69817dcd3d2a3fe35a18b Mon Sep 17 00:00:00 2001 From: kske Date: Mon, 3 Feb 2020 06:57:19 +0100 Subject: [PATCH 02/11] Fixed message reading --- src/main/java/envoy/client/Chat.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/envoy/client/Chat.java b/src/main/java/envoy/client/Chat.java index a00e46d..b51f068 100644 --- a/src/main/java/envoy/client/Chat.java +++ b/src/main/java/envoy/client/Chat.java @@ -45,15 +45,18 @@ public class Chat implements Serializable { public void appendMessage(Message message) { model.add(message); } /** - * Sets the status of all chat messages to {@code READ} starting from the bottom - * and stopping once a read message is found. - * + * 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 + * found. + * * @since Envoy v0.3-alpha */ public void read() { for (int i = model.size() - 1; i >= 0; --i) - if (model.get(i).getStatus() == MessageStatus.READ) break; - else model.get(i).setStatus(MessageStatus.READ); + if (model.get(i).getSenderId() == recipient.getId()) { + if (model.get(i).getStatus() == MessageStatus.READ) break; + else model.get(i).setStatus(MessageStatus.READ); + } } /** From d43b45d36b1719a23d90ebb2e2e0a15479571fbf Mon Sep 17 00:00:00 2001 From: kske Date: Mon, 3 Feb 2020 21:52:48 +0100 Subject: [PATCH 03/11] Made local database persistence optional * Split LocalDB into abstract class LocalDb and PersistentLocalDb and TransientLocalDb * Moved LocalDb to database package * Added ignoreLocalDb option to Config --- src/main/java/envoy/client/Client.java | 13 +- src/main/java/envoy/client/Config.java | 13 +- src/main/java/envoy/client/LocalDB.java | 163 ------------------ .../java/envoy/client/database/LocalDb.java | 119 +++++++++++++ .../client/database/PersistentLocalDb.java | 93 ++++++++++ .../client/database/TransientLocalDb.java | 15 ++ src/main/java/envoy/client/ui/ChatWindow.java | 37 ++-- src/main/java/envoy/client/ui/Startup.java | 48 ++++-- 8 files changed, 294 insertions(+), 207 deletions(-) delete mode 100644 src/main/java/envoy/client/LocalDB.java create mode 100644 src/main/java/envoy/client/database/LocalDb.java create mode 100644 src/main/java/envoy/client/database/PersistentLocalDb.java create mode 100644 src/main/java/envoy/client/database/TransientLocalDb.java diff --git a/src/main/java/envoy/client/Client.java b/src/main/java/envoy/client/Client.java index 8ed3bb4..a71f38e 100644 --- a/src/main/java/envoy/client/Client.java +++ b/src/main/java/envoy/client/Client.java @@ -9,6 +9,7 @@ import java.util.logging.Logger; import javax.naming.TimeLimitExceededException; +import envoy.client.database.LocalDb; import envoy.client.net.ReceivedMessageProcessor; import envoy.client.net.Receiver; import envoy.client.util.EnvoyLog; @@ -47,14 +48,14 @@ 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} + * @param credentials the login credentials of the user + * @param localDb the local database used to persist the current + * {@link IdGenerator} * @throws Exception if the online mode could not be entered or the request * failed for some other reason * @since Envoy v0.2-alpha */ - public void onlineInit(LoginCredentials credentials, LocalDB localDB) throws Exception { + public void onlineInit(LoginCredentials credentials, LocalDb localDb) 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()); @@ -92,10 +93,10 @@ public class Client implements Closeable { // TODO: Status handling // Process message ID generation - receiver.registerProcessor(IdGenerator.class, localDB::setIdGenerator); + receiver.registerProcessor(IdGenerator.class, localDb::setIdGenerator); // Request a generator if none is present - if (!localDB.hasIdGenerator() || !localDB.getIdGenerator().hasNext()) requestIdGenerator(); + if (!localDb.hasIdGenerator() || !localDb.getIdGenerator().hasNext()) requestIdGenerator(); } /** diff --git a/src/main/java/envoy/client/Config.java b/src/main/java/envoy/client/Config.java index fc93a08..24e7780 100644 --- a/src/main/java/envoy/client/Config.java +++ b/src/main/java/envoy/client/Config.java @@ -1,9 +1,7 @@ package envoy.client; import java.io.File; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; +import java.util.*; import java.util.logging.Level; import envoy.exception.EnvoyException; @@ -31,6 +29,7 @@ public class 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)); @@ -99,7 +98,7 @@ public class Config { * @return {@code true} if server, port and localDB directory are known. * @since Envoy v0.1-alpha */ - public boolean isInitialized() { return items.values().stream().noneMatch(item -> item.get() == null); } + public boolean isInitialized() { return items.values().stream().map(ConfigItem::get).noneMatch(Objects::isNull); } /** * @return the host name of the Envoy server @@ -119,6 +118,12 @@ public class Config { */ public File getLocalDB() { return (File) items.get("localDB").get(); } + /** + * @return {@code true} if the local database is to be ignored + * @since Envoy v0.3-alpha + */ + public Boolean isIgnoreLocalDB() { return (Boolean) items.get("ignoreLocalDB").get(); } + /** * @return the directory in which all local files are saves * @since Envoy v0.2-alpha diff --git a/src/main/java/envoy/client/LocalDB.java b/src/main/java/envoy/client/LocalDB.java deleted file mode 100644 index 7511d41..0000000 --- a/src/main/java/envoy/client/LocalDB.java +++ /dev/null @@ -1,163 +0,0 @@ -package envoy.client; - -import java.io.File; -import java.io.IOException; -import java.util.*; - -import envoy.data.IdGenerator; -import envoy.data.User; -import envoy.util.SerializationUtils; - -/** - * Stored information about the current {@link User} and their {@link Chat}s. - * For message ID generation a {@link IdGenerator} is stored as well. - * These object are persisted inside a folder of the local file system.
- *
- * Project: envoy-client
- * File: LocalDB.java
- * Created: 27.10.2019
- * - * @author Kai S. K. Engelbart - * @author Maximilian Käfer - * @since Envoy v0.1-alpha - */ -public class LocalDB { - - private File localDBDir, localDBFile, usersFile, idGeneratorFile; - private User user; - private Map users = new HashMap<>(); - private List chats = new ArrayList<>(); - private IdGenerator idGenerator; - - /** - * Constructs an empty local database. To serialize any chats to the file - * system, call {@link LocalDB#initializeDBFile()}. - * - * @param localDBDir the directory in which to store users and chats - * @throws IOException if the LocalDB could not be initialized - * @since Envoy v0.1-alpha - */ - public LocalDB(File localDBDir) throws IOException { - this.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"); - } - - /** - * Creates a database file for a user-specific list of chats. - * - * @throws NullPointerException if the client user is not yet specified - * @since Envoy v0.1-alpha - */ - public void initializeDBFile() { - if (user == null) throw new NullPointerException("Client user is null"); - localDBFile = new File(localDBDir, user.getId() + ".db"); - } - - /** - * Stores all users to the local database. If the client user is specified, the - * chats related to this user are stored as well. The message id generator will - * also be saved if present. - * - * @throws IOException if something went wrong during saving - * @since Envoy v0.1-alpha - */ - public void save() throws IOException { - // Save users - SerializationUtils.write(usersFile, users); - - // Save chats - if (user != null) SerializationUtils.write(localDBFile, chats); - - // Save id generator - if (hasIdGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator); - } - - /** - * Loads all users that are stored in the local database. - * - * @throws IOException if the loading process failed - * @throws ClassNotFoundException if the loading process failed - * @since Envoy v0.2-alpha - */ - public void loadUsers() throws ClassNotFoundException, IOException { users = SerializationUtils.read(usersFile, HashMap.class); } - - /** - * Loads all chats saved by Envoy for the client user. - * - * @throws IOException if the loading process failed - * @throws ClassNotFoundException if the loading process failed - * @since Envoy v0.1-alpha - */ - public void loadChats() throws ClassNotFoundException, IOException { chats = SerializationUtils.read(localDBFile, ArrayList.class); } - - /** - * Loads the message ID generator that is stored in the local database. If the - * file is not found, the exception is ignored. - * - * @since Envoy v0.3-alpha - */ - public void loadIdGenerator() { - try { - idGenerator = SerializationUtils.read(idGeneratorFile, IdGenerator.class); - } catch (ClassNotFoundException | IOException e) {} - } - - /** - * @return a {@code Map} of all users stored locally with their - * user names as keys - * @since Envoy v0.2-alpha - */ - 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 - * @since Envoy v0.1-alpha - **/ - public List getChats() { return chats; } - - /** - * @param chats the chats to set - */ - public void setChats(List chats) { this.chats = chats; } - - /** - * @return the {@link User} who initialized the local database - * @since Envoy v0.2-alpha - */ - public User getUser() { return user; } - - /** - * @param user the user to set - * @since Envoy v0.2-alpha - */ - public void setUser(User user) { this.user = user; } - - /** - * @return the message ID generator - * @since Envoy v0.3-alpha - */ - public IdGenerator getIdGenerator() { return idGenerator; } - - /** - * @param idGenerator the message ID generator to set - * @since Envoy v0.3-alpha - */ - public void setIdGenerator(IdGenerator idGenerator) { this.idGenerator = idGenerator; } - - /** - * @return {@code true} if an {@link IdGenerator} is present - * @since Envoy v0.3-alpha - */ - public boolean hasIdGenerator() { return idGenerator != null; } -} \ No newline at end of file diff --git a/src/main/java/envoy/client/database/LocalDb.java b/src/main/java/envoy/client/database/LocalDb.java new file mode 100644 index 0000000..58738e5 --- /dev/null +++ b/src/main/java/envoy/client/database/LocalDb.java @@ -0,0 +1,119 @@ +package envoy.client.database; + +import java.util.*; + +import envoy.client.Chat; +import envoy.data.IdGenerator; +import envoy.data.User; + +/** + * Stores information about the current {@link User} and their {@link Chat}s. + * For message ID generation a {@link IdGenerator} is stored as well.
+ *
+ * Project: envoy-client
+ * File: LocalDb.java
+ * Created: 3 Feb 2020
+ * + * @author Kai S. K. Engelbart + * @since Envoy v0.3-alpha + */ +public abstract class LocalDb { + + protected User user; + protected Map users = new HashMap<>(); + protected List chats = new ArrayList<>(); + protected IdGenerator idGenerator; + + /** + * Initializes a storage space for a user-specific list of chats. + * + * @since Envoy v0.3-alpha + */ + public void initializeUserStorage() {} + + /** + * Stores all users. If the client user is specified, their chats will be stored + * as well. The message id generator will also be saved if present. + * + * @throws Exception if the saving process failed + * @since Envoy v0.3-alpha + */ + public void save() throws Exception {} + + /** + * Loads all user data. + * + * @throws Exception if the loading process failed + * @since Envoy v0.3-alpha + */ + public void loadUsers() throws Exception {} + + /** + * Loads all chat data of the client user. + * + * @throws Exception if the loading process failed + * @since Envoy v0.3-alpha + */ + public void loadChats() throws Exception {} + + /** + * Loads the ID generator. Any exception thrown during this process is ignored. + * + * @since Envoy v0.3-alpha + */ + public void loadIdGenerator() {} + + /** + * @return a {@code Map} of all users stored locally with their + * user names as keys + * @since Envoy v0.2-alpha + */ + 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 + * @since Envoy v0.1-alpha + **/ + public List getChats() { return chats; } + + /** + * @param chats the chats to set + */ + public void setChats(List chats) { this.chats = chats; } + + /** + * @return the {@link User} who initialized the local database + * @since Envoy v0.2-alpha + */ + public User getUser() { return user; } + + /** + * @param user the user to set + * @since Envoy v0.2-alpha + */ + public void setUser(User user) { this.user = user; } + + /** + * @return the message ID generator + * @since Envoy v0.3-alpha + */ + public IdGenerator getIdGenerator() { return idGenerator; } + + /** + * @param idGenerator the message ID generator to set + * @since Envoy v0.3-alpha + */ + public void setIdGenerator(IdGenerator idGenerator) { this.idGenerator = idGenerator; } + + /** + * @return {@code true} if an {@link IdGenerator} is present + * @since Envoy v0.3-alpha + */ + public boolean hasIdGenerator() { return idGenerator != null; } +} diff --git a/src/main/java/envoy/client/database/PersistentLocalDb.java b/src/main/java/envoy/client/database/PersistentLocalDb.java new file mode 100644 index 0000000..d4d7523 --- /dev/null +++ b/src/main/java/envoy/client/database/PersistentLocalDb.java @@ -0,0 +1,93 @@ +package envoy.client.database; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +import envoy.client.ConfigItem; +import envoy.data.IdGenerator; +import envoy.util.SerializationUtils; + +/** + * Implements a {@link LocalDb} in a way that stores all information inside a + * folder on the local file system.
+ *
+ * Project: envoy-client
+ * File: PersistentLocalDb.java
+ * Created: 27.10.2019
+ * + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @since Envoy v0.1-alpha + */ +public class PersistentLocalDb extends LocalDb { + + private File localDBDir, localDBFile, usersFile, idGeneratorFile; + + /** + * 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}. + * + * @since Envoy 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 + * @since Envoy v0.1-alpha + */ + public PersistentLocalDb(File localDBDir) throws IOException { + this.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"); + } + + /** + * Creates a database file for a user-specific list of chats. + * + * @throws NullPointerException if the client user is not yet specified + * @since Envoy v0.1-alpha + */ + @Override + public void initializeUserStorage() { + if (user == null) throw new NullPointerException("Client user is null"); + localDBFile = new File(localDBDir, user.getId() + ".db"); + } + + @Override + public void save() throws IOException { + // Save users + SerializationUtils.write(usersFile, users); + + // Save chats + if (user != null) SerializationUtils.write(localDBFile, chats); + + // Save id generator + if (hasIdGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator); + } + + @Override + 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); } + + @Override + public void loadIdGenerator() { + try { + idGenerator = SerializationUtils.read(idGeneratorFile, IdGenerator.class); + } catch (ClassNotFoundException | IOException e) {} + } +} \ No newline at end of file diff --git a/src/main/java/envoy/client/database/TransientLocalDb.java b/src/main/java/envoy/client/database/TransientLocalDb.java new file mode 100644 index 0000000..f9124b1 --- /dev/null +++ b/src/main/java/envoy/client/database/TransientLocalDb.java @@ -0,0 +1,15 @@ +package envoy.client.database; + +/** + * Implements a {@link LocalDb} in a way that does not persist any information + * after application shutdown.
+ *
+ * Project: envoy-client
+ * File: TransientLocalDb.java
+ * Created: 3 Feb 2020
+ * + * @author Kai S. K. Engelbart + * @since Envoy v0.3-alpha + */ +public class TransientLocalDb extends LocalDb { +} diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index ab9e968..1263a16 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -9,7 +9,10 @@ import java.util.logging.Logger; import javax.swing.*; import javax.swing.border.EmptyBorder; -import envoy.client.*; +import envoy.client.Chat; +import envoy.client.Client; +import envoy.client.Settings; +import envoy.client.database.LocalDb; import envoy.client.event.MessageCreationEvent; import envoy.client.event.ThemeChangeEvent; import envoy.client.ui.list.ComponentList; @@ -34,7 +37,7 @@ public class ChatWindow extends JFrame { // User specific objects private Client client; - private LocalDB localDB; + private LocalDb localDb; // GUI components private JPanel contentPane = new JPanel(); @@ -161,13 +164,13 @@ public class ChatWindow extends JFrame { userList.setCellRenderer(new UserListRenderer()); userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); userList.addListSelectionListener((listSelectionEvent) -> { - if (client != null && localDB != null && !listSelectionEvent.getValueIsAdjusting()) { + if (client != null && localDb != null && !listSelectionEvent.getValueIsAdjusting()) { @SuppressWarnings("unchecked") final JList selectedUserList = (JList) listSelectionEvent.getSource(); final User user = selectedUserList.getSelectedValue(); // Select current chat - currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getId() == user.getId()).findFirst().get(); + currentChat = localDb.getChats().stream().filter(chat -> chat.getRecipient().getId() == user.getId()).findFirst().get(); // Read current Chat currentChat.read(); @@ -206,7 +209,7 @@ public class ChatWindow extends JFrame { // Listen to received messages EventBus.getInstance().register(MessageCreationEvent.class, (evt) -> { Message message = ((MessageCreationEvent) evt).get(); - localDB.getChats().stream().filter(c -> c.getRecipient().getId() == message.getSenderId()).findFirst().get().appendMessage(message); + localDb.getChats().stream().filter(c -> c.getRecipient().getId() == message.getSenderId()).findFirst().get().appendMessage(message); revalidate(); repaint(); }); @@ -262,7 +265,7 @@ public class ChatWindow extends JFrame { if (!messageEnterTextArea.getText().isEmpty()) try { // Create message - final Message message = new MessageBuilder(localDB.getUser().getId(), currentChat.getRecipient().getId(), localDB.getIdGenerator()) + final Message message = new MessageBuilder(localDb.getUser().getId(), currentChat.getRecipient().getId(), localDb.getIdGenerator()) .setText(messageEnterTextArea.getText()) .build(); @@ -270,7 +273,7 @@ public class ChatWindow extends JFrame { // TODO: Store offline messages client.sendMessage(message); - // Add message to LocalDB and update UI + // Add message to PersistentLocalDb and update UI currentChat.appendMessage(message); // messageList.setModel(currentChat.getModel()); @@ -281,8 +284,8 @@ public class ChatWindow extends JFrame { revalidate(); repaint(); - // Request a new id generator if all ids were used - if (!localDB.getIdGenerator().hasNext()) client.requestIdGenerator(); + // Request a new id generator if all IDs were used + if (!localDb.getIdGenerator().hasNext()) client.requestIdGenerator(); } catch (Exception e) { JOptionPane.showMessageDialog(this, @@ -302,12 +305,12 @@ public class ChatWindow extends JFrame { private void loadUsersAndChats() { new Thread(() -> { DefaultListModel userListModel = new DefaultListModel<>(); - localDB.getUsers().values().forEach(user -> { + localDb.getUsers().values().forEach(user -> { userListModel.addElement(user); // Check if user exists in local DB - if (localDB.getChats().stream().filter(c -> c.getRecipient().getId() == user.getId()).count() == 0) - localDB.getChats().add(new Chat(user)); + if (localDb.getChats().stream().filter(c -> c.getRecipient().getId() == user.getId()).count() == 0) + localDb.getChats().add(new Chat(user)); }); SwingUtilities.invokeLater(() -> userList.setModel(userListModel)); }).start(); @@ -324,14 +327,16 @@ public class ChatWindow extends JFrame { } /** - * Sets the {@link LocalDB} used by this {@link ChatWindow}. After invoking this + * 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 + * @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 setLocalDB(LocalDb localDb) { + this.localDb = localDb; loadUsersAndChats(); } } diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 949b464..0f564f8 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -11,7 +11,12 @@ import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; -import envoy.client.*; +import envoy.client.Client; +import envoy.client.Config; +import envoy.client.Settings; +import envoy.client.database.LocalDb; +import envoy.client.database.PersistentLocalDb; +import envoy.client.database.TransientLocalDb; import envoy.client.util.EnvoyLog; import envoy.data.LoginCredentials; import envoy.data.User; @@ -63,8 +68,8 @@ public class Startup { } catch (Exception e) { JOptionPane .showMessageDialog(null, "Error loading configuration values:\n" + e.toString(), "Configuration error", JOptionPane.ERROR_MESSAGE); - System.exit(1); e.printStackTrace(); + System.exit(1); } // Set new logger levels loaded from config @@ -80,12 +85,19 @@ public class Startup { } // Initialize the local database - LocalDB localDB; - try { - localDB = new LocalDB(new File(config.getHomeDirectory(), config.getLocalDB().getPath())); + LocalDb localDb; + if (config.isIgnoreLocalDB()) { + localDb = new TransientLocalDb(); + JOptionPane.showMessageDialog(null, + "Ignoring local database.\nMessages will not be saved!", + "Local database warning", + JOptionPane.WARNING_MESSAGE); + } else try { + localDb = new PersistentLocalDb(new File(config.getHomeDirectory(), config.getLocalDB().getPath())); } catch (IOException e3) { logger.log(Level.SEVERE, "Could not initialize local database", e3); - JOptionPane.showMessageDialog(null, "Could not initialize local database!\n" + e3.toString()); + JOptionPane + .showMessageDialog(null, "Could not initialize local database!\n" + e3.toString(), "Local database error", JOptionPane.ERROR_MESSAGE); System.exit(1); return; } @@ -97,15 +109,15 @@ public class Startup { Client client = new Client(); try { // Try entering online mode first - localDB.loadIdGenerator(); - client.onlineInit(credentials, localDB); + localDb.loadIdGenerator(); + client.onlineInit(credentials, localDb); } catch (Exception e1) { logger.warning("Could not connect to server. Trying offline mode..."); e1.printStackTrace(); try { // Try entering offline mode - localDB.loadUsers(); - User clientUser = localDB.getUsers().get(credentials.getName()); + localDb.loadUsers(); + User clientUser = localDb.getUsers().get(credentials.getName()); if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown"); client.setSender(clientUser); JOptionPane.showMessageDialog(null, @@ -120,12 +132,12 @@ public class Startup { } // Set client user in local database - localDB.setUser(client.getSender()); + localDb.setUser(client.getSender()); // Initialize chats in local database try { - localDB.initializeDBFile(); - localDB.loadChats(); + localDb.initializeUserStorage(); + localDb.loadChats(); } catch (FileNotFoundException e) { // The local database file has not yet been created, probably first login } catch (Exception e) { @@ -137,12 +149,12 @@ public class Startup { } // Save all users to the local database - if (client.isOnline()) localDB.setUsers(client.getUsers()); + if (client.isOnline()) localDb.setUsers(client.getUsers()); EventQueue.invokeLater(() -> { try { chatWindow.setClient(client); - chatWindow.setLocalDB(localDB); + chatWindow.setLocalDB(localDb); try { new StatusTrayIcon(chatWindow).show(); @@ -162,16 +174,16 @@ public class Startup { } }); - // Save Settings and LocalDB on shutdown + // Save Settings and PersistentLocalDb on shutdown Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { logger.info("Closing connection..."); client.close(); logger.info("Saving local database and settings..."); - localDB.save(); + localDb.save(); Settings.getInstance().save(); - } catch (IOException e) { + } catch (Exception e) { logger.log(Level.SEVERE, "Unable to save local files", e); } })); From 7e0ae2e8310587dda485e26b6d2055a2bb7096f9 Mon Sep 17 00:00:00 2001 From: kske Date: Mon, 3 Feb 2020 22:06:56 +0100 Subject: [PATCH 04/11] Added Chat#isUnread() for future use in message notifications --- src/main/java/envoy/client/Chat.java | 7 +++++++ src/main/java/envoy/client/ui/list/ComponentListModel.java | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/main/java/envoy/client/Chat.java b/src/main/java/envoy/client/Chat.java index b51f068..1a54e42 100644 --- a/src/main/java/envoy/client/Chat.java +++ b/src/main/java/envoy/client/Chat.java @@ -59,6 +59,13 @@ public class Chat implements Serializable { } } + /** + * @return {@code true} if the newest message received in the chat doesn't have + * the status {@code READ} + * @since Envoy v0.3-alpha + */ + public boolean isUnread() { return !model.isEmpty() && model.get(model.size() - 1).getStatus() != MessageStatus.READ; } + /** * @return all messages in the current chat * @since Envoy v0.1-alpha diff --git a/src/main/java/envoy/client/ui/list/ComponentListModel.java b/src/main/java/envoy/client/ui/list/ComponentListModel.java index 8fb1d68..fff3e6c 100644 --- a/src/main/java/envoy/client/ui/list/ComponentListModel.java +++ b/src/main/java/envoy/client/ui/list/ComponentListModel.java @@ -78,6 +78,12 @@ public final class ComponentListModel implements Iterable, Serializable { */ public int size() { return elements.size(); } + /** + * @return {@code true} if this model contains no elements + * @see java.util.List#isEmpty() + */ + public boolean isEmpty() { return elements.isEmpty(); } + /** * @return an iterator over the elements of this list model * @see java.util.List#iterator() From 44541936d3fb72271b8fc56c4bb52b7d518e2836 Mon Sep 17 00:00:00 2001 From: kske Date: Tue, 4 Feb 2020 19:13:31 +0100 Subject: [PATCH 05/11] Implemented receiving unread messages using a message cache Fixes #98 --- src/main/java/envoy/client/Chat.java | 4 +- src/main/java/envoy/client/Client.java | 26 ++++++--- .../java/envoy/client/net/MessageCache.java | 54 +++++++++++++++++++ src/main/java/envoy/client/ui/Startup.java | 10 +++- 4 files changed, 84 insertions(+), 10 deletions(-) create mode 100644 src/main/java/envoy/client/net/MessageCache.java diff --git a/src/main/java/envoy/client/Chat.java b/src/main/java/envoy/client/Chat.java index 1a54e42..b75175f 100644 --- a/src/main/java/envoy/client/Chat.java +++ b/src/main/java/envoy/client/Chat.java @@ -24,8 +24,8 @@ public class Chat implements Serializable { private static final long serialVersionUID = -7751248474547242056L; - private User recipient; - private ComponentListModel model = new ComponentListModel<>(); + private final User recipient; + private final ComponentListModel model = new ComponentListModel<>(); /** * Provides the list of messages that the recipient receives.
diff --git a/src/main/java/envoy/client/Client.java b/src/main/java/envoy/client/Client.java index a71f38e..145c1bb 100644 --- a/src/main/java/envoy/client/Client.java +++ b/src/main/java/envoy/client/Client.java @@ -10,6 +10,7 @@ import java.util.logging.Logger; import javax.naming.TimeLimitExceededException; import envoy.client.database.LocalDb; +import envoy.client.net.MessageCache; import envoy.client.net.ReceivedMessageProcessor; import envoy.client.net.Receiver; import envoy.client.util.EnvoyLog; @@ -48,14 +49,16 @@ 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} + * @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 * @throws Exception if the online mode could not be entered or the request * failed for some other reason * @since Envoy v0.2-alpha */ - public void onlineInit(LoginCredentials credentials, LocalDb localDb) throws Exception { + public MessageCache onlineInit(LoginCredentials credentials, LocalDb localDb) 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()); @@ -64,9 +67,13 @@ public class Client implements Closeable { // Create message receiver receiver = new Receiver(socket.getInputStream()); - // Register user creation processor + // 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); // Start receiver new Thread(receiver).start(); @@ -89,7 +96,12 @@ public class Client implements Closeable { receiver.removeAllProcessors(); // Register processors for message and status handling - receiver.registerProcessor(Message.class, new ReceivedMessageProcessor()); + final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor(); + receiver.registerProcessor(Message.class, receivedMessageProcessor); + + // Relay cached unread messages + cache.setProcessor(receivedMessageProcessor); + // TODO: Status handling // Process message ID generation @@ -97,6 +109,8 @@ public class Client implements Closeable { // Request a generator if none is present if (!localDb.hasIdGenerator() || !localDb.getIdGenerator().hasNext()) requestIdGenerator(); + + return cache; } /** diff --git a/src/main/java/envoy/client/net/MessageCache.java b/src/main/java/envoy/client/net/MessageCache.java new file mode 100644 index 0000000..1fc49c3 --- /dev/null +++ b/src/main/java/envoy/client/net/MessageCache.java @@ -0,0 +1,54 @@ +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/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 0f564f8..1bdbe73 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -17,6 +17,7 @@ import envoy.client.Settings; import envoy.client.database.LocalDb; import envoy.client.database.PersistentLocalDb; import envoy.client.database.TransientLocalDb; +import envoy.client.net.MessageCache; import envoy.client.util.EnvoyLog; import envoy.data.LoginCredentials; import envoy.data.User; @@ -106,11 +107,12 @@ 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(); + Client client = new Client(); + MessageCache cache = null; try { // Try entering online mode first localDb.loadIdGenerator(); - client.onlineInit(credentials, localDb); + cache = client.onlineInit(credentials, localDb); } catch (Exception e1) { logger.warning("Could not connect to server. Trying offline mode..."); e1.printStackTrace(); @@ -151,6 +153,7 @@ public class Startup { // Save all users to the local database if (client.isOnline()) localDb.setUsers(client.getUsers()); + // Display ChatWindow and StatusTrayIcon EventQueue.invokeLater(() -> { try { chatWindow.setClient(client); @@ -174,6 +177,9 @@ 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 { From 8f967afa88242dddfcd4e48b833d77c5e6ec7611 Mon Sep 17 00:00:00 2001 From: kske Date: Tue, 4 Feb 2020 19:46:18 +0100 Subject: [PATCH 06/11] Moved client to net package, removed unnecessary recipient property --- .../java/envoy/client/{ => net}/Client.java | 41 +++++-------------- src/main/java/envoy/client/ui/ChatWindow.java | 8 ++-- src/main/java/envoy/client/ui/Startup.java | 2 +- 3 files changed, 15 insertions(+), 36 deletions(-) rename src/main/java/envoy/client/{ => net}/Client.java (88%) diff --git a/src/main/java/envoy/client/Client.java b/src/main/java/envoy/client/net/Client.java similarity index 88% rename from src/main/java/envoy/client/Client.java rename to src/main/java/envoy/client/net/Client.java index 145c1bb..0920aab 100644 --- a/src/main/java/envoy/client/Client.java +++ b/src/main/java/envoy/client/net/Client.java @@ -1,4 +1,4 @@ -package envoy.client; +package envoy.client.net; import java.io.Closeable; import java.io.IOException; @@ -9,16 +9,17 @@ import java.util.logging.Logger; import javax.naming.TimeLimitExceededException; +import envoy.client.Config; import envoy.client.database.LocalDb; -import envoy.client.net.MessageCache; -import envoy.client.net.ReceivedMessageProcessor; -import envoy.client.net.Receiver; import envoy.client.util.EnvoyLog; import envoy.data.*; import envoy.event.IdGeneratorRequest; import envoy.util.SerializationUtils; /** + * Establishes a connection to the server, performs a handshake and delivers + * certain objects to the server.
+ *
* Project: envoy-client
* File: Client.java
* Created: 28 Sep 2019
@@ -30,17 +31,17 @@ import envoy.util.SerializationUtils; */ public class Client implements Closeable { + // Connection handling private Socket socket; private Receiver receiver; private boolean online; - private volatile User sender; - private User recipient; - - private volatile Contacts contacts; - - private Config config = Config.getInstance(); + // Asynchronously initialized during handshake + private volatile User sender; + private volatile Contacts contacts; + // Configuration and logging + private static final Config config = Config.getInstance(); private static final Logger logger = EnvoyLog.getLogger(Client.class.getSimpleName()); /** @@ -172,26 +173,6 @@ public class Client implements Closeable { */ public void setSender(User sender) { this.sender = sender; } - /** - * @return the current recipient of the current chat. - * @since Envoy v0.1-alpha - */ - public User getRecipient() { return recipient; } - - /** - * Sets the recipient. - * - * @param recipient the recipient to set - * @since Envoy v0.1-alpha - */ - public void setRecipient(User recipient) { this.recipient = recipient; } - - /** - * @return true, if a recipient is selected - * @since Envoy v0.1-alpha - */ - public boolean hasRecipient() { return recipient != null; } - /** * @return the {@link Receiver} used by this {@link Client} */ diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index 1263a16..b92446a 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -10,11 +10,11 @@ import javax.swing.*; import javax.swing.border.EmptyBorder; import envoy.client.Chat; -import envoy.client.Client; import envoy.client.Settings; import envoy.client.database.LocalDb; import envoy.client.event.MessageCreationEvent; import envoy.client.event.ThemeChangeEvent; +import envoy.client.net.Client; import envoy.client.ui.list.ComponentList; import envoy.client.ui.settings.SettingsScreen; import envoy.client.util.EnvoyLog; @@ -175,8 +175,7 @@ public class ChatWindow extends JFrame { // Read current Chat currentChat.read(); - // Set recipient in client and chat title - client.setRecipient(user); + // Set chat title textPane.setText(currentChat.getRecipient().getName()); // Update model and scroll down @@ -257,7 +256,7 @@ public class ChatWindow extends JFrame { } private void postMessage() { - if (!client.hasRecipient()) { + if (userList.isSelectionEmpty()) { JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE); return; } @@ -275,7 +274,6 @@ public class ChatWindow extends JFrame { // Add message to PersistentLocalDb and update UI currentChat.appendMessage(message); - // messageList.setModel(currentChat.getModel()); // Clear text field messageEnterTextArea.setText(""); diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 1bdbe73..272e1ec 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -11,12 +11,12 @@ import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; -import envoy.client.Client; import envoy.client.Config; import envoy.client.Settings; import envoy.client.database.LocalDb; import envoy.client.database.PersistentLocalDb; import envoy.client.database.TransientLocalDb; +import envoy.client.net.Client; import envoy.client.net.MessageCache; import envoy.client.util.EnvoyLog; import envoy.data.LoginCredentials; From 5e335a98bde9a1cec768d9969bd2c3be40ed4a71 Mon Sep 17 00:00:00 2001 From: kske Date: Wed, 5 Feb 2020 07:09:25 +0100 Subject: [PATCH 07/11] Listening to message status changes, sending READ status updates --- src/main/java/envoy/client/Chat.java | 24 +++++++++--- src/main/java/envoy/client/net/Client.java | 20 ++++++++-- .../MessageStatusChangeEventProcessor.java | 38 ++++++++++++++++++ src/main/java/envoy/client/ui/ChatWindow.java | 39 +++++++++++++++---- 4 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java diff --git a/src/main/java/envoy/client/Chat.java b/src/main/java/envoy/client/Chat.java index b75175f..e67eb34 100644 --- a/src/main/java/envoy/client/Chat.java +++ b/src/main/java/envoy/client/Chat.java @@ -1,11 +1,14 @@ package envoy.client; +import java.io.IOException; import java.io.Serializable; +import envoy.client.net.Client; import envoy.client.ui.list.ComponentListModel; import envoy.data.Message; import envoy.data.Message.MessageStatus; import envoy.data.User; +import envoy.event.MessageStatusChangeEvent; /** * Represents a chat between two {@link User}s
@@ -49,14 +52,25 @@ 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 + * @throws IOException if a {@link MessageStatusChangeEvent} could not be + * delivered to the server * @since Envoy v0.3-alpha */ - public void read() { - for (int i = model.size() - 1; i >= 0; --i) - if (model.get(i).getSenderId() == recipient.getId()) { - if (model.get(i).getStatus() == MessageStatus.READ) break; - else model.get(i).setStatus(MessageStatus.READ); + public void read(Client client) 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)); + } } + } } /** diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java index 0920aab..31b1d59 100644 --- a/src/main/java/envoy/client/net/Client.java +++ b/src/main/java/envoy/client/net/Client.java @@ -13,7 +13,9 @@ import envoy.client.Config; import envoy.client.database.LocalDb; import envoy.client.util.EnvoyLog; import envoy.data.*; +import envoy.event.Event; import envoy.event.IdGeneratorRequest; +import envoy.event.MessageStatusChangeEvent; import envoy.util.SerializationUtils; /** @@ -42,7 +44,7 @@ public class Client implements Closeable { // Configuration and logging private static final Config config = Config.getInstance(); - private static final Logger logger = EnvoyLog.getLogger(Client.class.getSimpleName()); + private static final Logger logger = EnvoyLog.getLogger(Client.class.getSimpleName()); /** * Enters the online mode by acquiring a user ID from the server. As a @@ -103,19 +105,21 @@ public class Client implements Closeable { // Relay cached unread messages cache.setProcessor(receivedMessageProcessor); - // TODO: Status handling + // Process message status changes + receiver.registerProcessor(MessageStatusChangeEvent.class, new MessageStatusChangeEventProcessor()); // Process message ID generation receiver.registerProcessor(IdGenerator.class, localDb::setIdGenerator); - // Request a generator if none is present + // Request a generator if none is present or the existing one is consumed if (!localDb.hasIdGenerator() || !localDb.getIdGenerator().hasNext()) requestIdGenerator(); return cache; } /** - * Sends a message to the server. + * Sends a message to the server. The message's status will be incremented once + * it was delivered successfully. * * @param message the message to send * @throws IOException if the message does not reach the server @@ -126,6 +130,14 @@ public class Client implements Closeable { message.nextStatus(); } + /** + * Sends an event to the server. + * + * @param evt the event to send + * @throws IOException if the event did not reach the server + */ + public void sendEvent(Event evt) throws IOException { writeObject(evt); } + /** * Requests a new {@link IdGenerator} from the server. * diff --git a/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java b/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java new file mode 100644 index 0000000..1ceecf1 --- /dev/null +++ b/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java @@ -0,0 +1,38 @@ +package envoy.client.net; + +import java.util.function.Consumer; +import java.util.logging.Logger; + +import envoy.client.util.EnvoyLog; +import envoy.data.Message.MessageStatus; +import envoy.event.EventBus; +import envoy.event.MessageStatusChangeEvent; + +/** + * Project: envoy-client
+ * File: MessageStatusChangeEventProcessor.java
+ * Created: 4 Feb 2020
+ * + * @author Kai S. K. Engelbart + * @since Envoy v0.3-alpha + */ +public class MessageStatusChangeEventProcessor implements Consumer { + + private static final Logger logger = EnvoyLog.getLogger(MessageStatusChangeEventProcessor.class.getSimpleName()); + + /** + * Dispatches a {@link MessageStatusChangeEvent} if the status is + * {@code RECEIVED} or {@code READ}. + * + * @param evt the status change event + * @since Envoy v0.3-alpha + */ + @Override + public void accept(MessageStatusChangeEvent evt) { + if (evt.get().ordinal() <= MessageStatus.RECEIVED.ordinal()) logger.info("Received invalid message status change " + evt); + else { + logger.info("Received " + evt.toString()); + EventBus.getInstance().dispatch(evt); + } + } +} diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index b92446a..aba5726 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -3,6 +3,7 @@ package envoy.client.ui; import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; +import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -19,9 +20,11 @@ import envoy.client.ui.list.ComponentList; import envoy.client.ui.settings.SettingsScreen; import envoy.client.util.EnvoyLog; import envoy.data.Message; +import envoy.data.Message.MessageStatus; import envoy.data.MessageBuilder; import envoy.data.User; import envoy.event.EventBus; +import envoy.event.MessageStatusChangeEvent; /** * Project: envoy-client
@@ -173,7 +176,12 @@ public class ChatWindow extends JFrame { currentChat = localDb.getChats().stream().filter(chat -> chat.getRecipient().getId() == user.getId()).findFirst().get(); // Read current Chat - currentChat.read(); + try { + currentChat.read(client); + } catch (IOException e) { + e.printStackTrace(); + logger.log(Level.WARNING, "Could notify server about message status change", e); + } // Set chat title textPane.setText(currentChat.getRecipient().getName()); @@ -213,6 +221,26 @@ public class ChatWindow extends JFrame { repaint(); }); + // Listen to message status changes + EventBus.getInstance().register(MessageStatusChangeEvent.class, (evt) -> { + final long id = ((MessageStatusChangeEvent) evt).getId(); + final MessageStatus status = (MessageStatus) evt.get(); + + for (Chat c : localDb.getChats()) + for (Message m : c.getModel()) + if (m.getId() == id) { + + // Update message status + m.setStatus(status); + + // Update model and scroll down if current chat + if (c == currentChat) { + messageList.setModel(currentChat.getModel()); + scrollPane.setChatOpened(true); + } + } + }); + revalidate(); } @@ -286,10 +314,7 @@ public class ChatWindow extends JFrame { if (!localDb.getIdGenerator().hasNext()) client.requestIdGenerator(); } catch (Exception e) { - JOptionPane.showMessageDialog(this, - "Error sending message:\n" + e.toString(), - "Message sending error", - JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, "Error sending message:\n" + e.toString(), "Message sending error", JOptionPane.ERROR_MESSAGE); e.printStackTrace(); } } @@ -320,9 +345,7 @@ public class ChatWindow extends JFrame { * @param client the {@link Client} used to send and receive messages * @since Envoy v0.2-alpha */ - public void setClient(Client client) { - this.client = client; - } + public void setClient(Client client) { this.client = client; } /** * Sets the {@link LocalDb} used by this {@link ChatWindow}. After From 58b9ac808175b8cd0e7b27e834abc2b79cec8917 Mon Sep 17 00:00:00 2001 From: kske Date: Wed, 5 Feb 2020 16:12:10 +0100 Subject: [PATCH 08/11] Updating status of received messages to RECEIVED --- .../java/envoy/client/net/ReceivedMessageProcessor.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/envoy/client/net/ReceivedMessageProcessor.java b/src/main/java/envoy/client/net/ReceivedMessageProcessor.java index 27b505e..ba0a386 100644 --- a/src/main/java/envoy/client/net/ReceivedMessageProcessor.java +++ b/src/main/java/envoy/client/net/ReceivedMessageProcessor.java @@ -25,8 +25,12 @@ public class ReceivedMessageProcessor implements Consumer { public void accept(Message message) { logger.info("Received message object " + message); if (message.getStatus() != MessageStatus.SENT) logger.warning("The message has the unexpected status " + message.getStatus()); - else + else { + // Update status to RECEIVED + message.nextStatus(); + // Dispatch event EventBus.getInstance().dispatch(new MessageCreationEvent(message)); + } } } From 8f4cf1428aff9b4a2cb4c892cc50df66b74b8051 Mon Sep 17 00:00:00 2001 From: DieGurke <55625494+DieGurke@users.noreply.github.com> Date: Wed, 5 Feb 2020 17:23:30 +0100 Subject: [PATCH 09/11] Reading current chat when a new message is received --- .../MessageStatusChangeEventProcessor.java | 2 +- src/main/java/envoy/client/ui/ChatWindow.java | 15 +++++++++++- .../envoy/client/ui/list/ComponentList.java | 24 +++++++++---------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java b/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java index 1ceecf1..1cf457f 100644 --- a/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java +++ b/src/main/java/envoy/client/net/MessageStatusChangeEventProcessor.java @@ -29,7 +29,7 @@ public class MessageStatusChangeEventProcessor implements Consumer { Message message = ((MessageCreationEvent) evt).get(); - localDb.getChats().stream().filter(c -> c.getRecipient().getId() == message.getSenderId()).findFirst().get().appendMessage(message); + Chat chat = localDb.getChats().stream().filter(c -> c.getRecipient().getId() == message.getSenderId()).findFirst().get(); + chat.appendMessage(message); + + // Read message and update UI if in current chat + if (chat == currentChat) { + try { + currentChat.read(client); + } catch (IOException e) { + e.printStackTrace(); + logger.log(Level.WARNING, "Could notify server about message status change", e); + } + messageList.synchronizeModel(); + } + revalidate(); repaint(); }); diff --git a/src/main/java/envoy/client/ui/list/ComponentList.java b/src/main/java/envoy/client/ui/list/ComponentList.java index 8cbf2db..f62dc7b 100644 --- a/src/main/java/envoy/client/ui/list/ComponentList.java +++ b/src/main/java/envoy/client/ui/list/ComponentList.java @@ -66,6 +66,18 @@ public class ComponentList extends JPanel { synchronizeModel(); } + /** + * Removes all child components and then adds all components representing the + * elements of the {@link ComponentListModel}. + * + * @since Envoy v0.3-alpha + */ + public void synchronizeModel() { + removeAll(); + if (model != null) for (E elem : model) + add(elem); + } + /** * Adds an object to the list by rendering it with the current * {@link ComponentListCellRenderer}. @@ -76,16 +88,4 @@ public class ComponentList extends JPanel { void add(E elem) { add(renderer.getListCellComponent(this, elem, false)); } - - /** - * Removes all child components and then adds all components representing the - * elements of the {@link ComponentListModel}. - * - * @since Envoy v0.3-alpha - */ - void synchronizeModel() { - removeAll(); - if (model != null) for (E elem : model) - add(elem); - } } From d005ed8d5071c66cc7f90f2d8ffe26ca182894ec Mon Sep 17 00:00:00 2001 From: kske Date: Wed, 5 Feb 2020 20:08:24 +0100 Subject: [PATCH 10/11] Fixed UI update after message status changes --- src/main/java/envoy/client/event/MessageCreationEvent.java | 1 + src/main/java/envoy/client/event/MessageEvent.java | 1 + .../java/envoy/client/event/MessageModificationEvent.java | 1 + src/main/java/envoy/client/ui/ChatWindow.java | 6 +++++- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/envoy/client/event/MessageCreationEvent.java b/src/main/java/envoy/client/event/MessageCreationEvent.java index 301603e..72e47f5 100644 --- a/src/main/java/envoy/client/event/MessageCreationEvent.java +++ b/src/main/java/envoy/client/event/MessageCreationEvent.java @@ -8,6 +8,7 @@ import envoy.data.Message; * Created: 4 Dec 2019
* * @author Kai S. K. Engelbart + * @since Envoy v0.2-alpha */ public class MessageCreationEvent extends MessageEvent { diff --git a/src/main/java/envoy/client/event/MessageEvent.java b/src/main/java/envoy/client/event/MessageEvent.java index 8bd05d1..fb50109 100644 --- a/src/main/java/envoy/client/event/MessageEvent.java +++ b/src/main/java/envoy/client/event/MessageEvent.java @@ -9,6 +9,7 @@ import envoy.event.Event; * Created: 4 Dec 2019
* * @author Kai S. K. Engelbart + * @since Envoy v0.2-alpha */ public class MessageEvent implements Event { diff --git a/src/main/java/envoy/client/event/MessageModificationEvent.java b/src/main/java/envoy/client/event/MessageModificationEvent.java index 3a5549a..4077383 100644 --- a/src/main/java/envoy/client/event/MessageModificationEvent.java +++ b/src/main/java/envoy/client/event/MessageModificationEvent.java @@ -8,6 +8,7 @@ import envoy.data.Message; * Created: 4 Dec 2019
* * @author Kai S. K. Engelbart + * @since Envoy v0.2-alpha */ public class MessageModificationEvent extends MessageEvent { diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index 81d04f6..d018b40 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -190,6 +190,7 @@ public class ChatWindow extends JFrame { messageList.setModel(currentChat.getModel()); scrollPane.setChatOpened(true); + messageList.synchronizeModel(); revalidate(); repaint(); } @@ -250,8 +251,11 @@ public class ChatWindow extends JFrame { if (c == currentChat) { messageList.setModel(currentChat.getModel()); scrollPane.setChatOpened(true); - } + } else messageList.synchronizeModel(); } + + revalidate(); + repaint(); }); revalidate(); From 81dcee27f18cfd1ff37fe45f766787a539daaf4d Mon Sep 17 00:00:00 2001 From: kske Date: Wed, 5 Feb 2020 20:58:30 +0100 Subject: [PATCH 11/11] Renamed database package to data, moved Chat to data package --- .../java/envoy/client/{ => data}/Chat.java | 2 +- .../client/{database => data}/LocalDb.java | 3 +- .../{database => data}/PersistentLocalDb.java | 2 +- .../{database => data}/TransientLocalDb.java | 2 +- src/main/java/envoy/client/net/Client.java | 2 +- src/main/java/envoy/client/ui/ChatWindow.java | 33 +++++++++---------- src/main/java/envoy/client/ui/Startup.java | 6 ++-- 7 files changed, 23 insertions(+), 27 deletions(-) rename src/main/java/envoy/client/{ => data}/Chat.java (99%) rename src/main/java/envoy/client/{database => data}/LocalDb.java (98%) rename src/main/java/envoy/client/{database => data}/PersistentLocalDb.java (98%) rename src/main/java/envoy/client/{database => data}/TransientLocalDb.java (92%) diff --git a/src/main/java/envoy/client/Chat.java b/src/main/java/envoy/client/data/Chat.java similarity index 99% rename from src/main/java/envoy/client/Chat.java rename to src/main/java/envoy/client/data/Chat.java index e67eb34..f10e816 100644 --- a/src/main/java/envoy/client/Chat.java +++ b/src/main/java/envoy/client/data/Chat.java @@ -1,4 +1,4 @@ -package envoy.client; +package envoy.client.data; import java.io.IOException; import java.io.Serializable; diff --git a/src/main/java/envoy/client/database/LocalDb.java b/src/main/java/envoy/client/data/LocalDb.java similarity index 98% rename from src/main/java/envoy/client/database/LocalDb.java rename to src/main/java/envoy/client/data/LocalDb.java index 58738e5..79368a8 100644 --- a/src/main/java/envoy/client/database/LocalDb.java +++ b/src/main/java/envoy/client/data/LocalDb.java @@ -1,8 +1,7 @@ -package envoy.client.database; +package envoy.client.data; import java.util.*; -import envoy.client.Chat; import envoy.data.IdGenerator; import envoy.data.User; diff --git a/src/main/java/envoy/client/database/PersistentLocalDb.java b/src/main/java/envoy/client/data/PersistentLocalDb.java similarity index 98% rename from src/main/java/envoy/client/database/PersistentLocalDb.java rename to src/main/java/envoy/client/data/PersistentLocalDb.java index d4d7523..7856517 100644 --- a/src/main/java/envoy/client/database/PersistentLocalDb.java +++ b/src/main/java/envoy/client/data/PersistentLocalDb.java @@ -1,4 +1,4 @@ -package envoy.client.database; +package envoy.client.data; import java.io.File; import java.io.IOException; diff --git a/src/main/java/envoy/client/database/TransientLocalDb.java b/src/main/java/envoy/client/data/TransientLocalDb.java similarity index 92% rename from src/main/java/envoy/client/database/TransientLocalDb.java rename to src/main/java/envoy/client/data/TransientLocalDb.java index f9124b1..433488a 100644 --- a/src/main/java/envoy/client/database/TransientLocalDb.java +++ b/src/main/java/envoy/client/data/TransientLocalDb.java @@ -1,4 +1,4 @@ -package envoy.client.database; +package envoy.client.data; /** * Implements a {@link LocalDb} in a way that does not persist any information diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java index 31b1d59..91e9040 100644 --- a/src/main/java/envoy/client/net/Client.java +++ b/src/main/java/envoy/client/net/Client.java @@ -10,7 +10,7 @@ import java.util.logging.Logger; import javax.naming.TimeLimitExceededException; import envoy.client.Config; -import envoy.client.database.LocalDb; +import envoy.client.data.LocalDb; import envoy.client.util.EnvoyLog; import envoy.data.*; import envoy.event.Event; diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index d018b40..3abd54a 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -10,9 +10,9 @@ import java.util.logging.Logger; import javax.swing.*; import javax.swing.border.EmptyBorder; -import envoy.client.Chat; import envoy.client.Settings; -import envoy.client.database.LocalDb; +import envoy.client.data.Chat; +import envoy.client.data.LocalDb; import envoy.client.event.MessageCreationEvent; import envoy.client.event.ThemeChangeEvent; import envoy.client.net.Client; @@ -175,13 +175,8 @@ public class ChatWindow extends JFrame { // Select current chat currentChat = localDb.getChats().stream().filter(chat -> chat.getRecipient().getId() == user.getId()).findFirst().get(); - // Read current Chat - try { - currentChat.read(client); - } catch (IOException e) { - e.printStackTrace(); - logger.log(Level.WARNING, "Could notify server about message status change", e); - } + // Read current chat + readCurrentChat(); // Set chat title textPane.setText(currentChat.getRecipient().getName()); @@ -221,15 +216,7 @@ public class ChatWindow extends JFrame { chat.appendMessage(message); // Read message and update UI if in current chat - if (chat == currentChat) { - try { - currentChat.read(client); - } catch (IOException e) { - e.printStackTrace(); - logger.log(Level.WARNING, "Could notify server about message status change", e); - } - messageList.synchronizeModel(); - } + if (chat == currentChat) readCurrentChat(); revalidate(); repaint(); @@ -356,6 +343,16 @@ public class ChatWindow extends JFrame { }).start(); } + private void readCurrentChat() { + try { + currentChat.read(client); + messageList.synchronizeModel(); + } catch (IOException e) { + e.printStackTrace(); + logger.log(Level.WARNING, "Couldn't notify server about message status change", e); + } + } + /** * Sets the {@link Client} used by this {@link ChatWindow}. * diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 272e1ec..9f308dc 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -13,9 +13,9 @@ import javax.swing.SwingUtilities; import envoy.client.Config; import envoy.client.Settings; -import envoy.client.database.LocalDb; -import envoy.client.database.PersistentLocalDb; -import envoy.client.database.TransientLocalDb; +import envoy.client.data.LocalDb; +import envoy.client.data.PersistentLocalDb; +import envoy.client.data.TransientLocalDb; import envoy.client.net.Client; import envoy.client.net.MessageCache; import envoy.client.util.EnvoyLog;