diff --git a/src/main/java/envoy/client/data/Cache.java b/src/main/java/envoy/client/data/Cache.java index 0707a62..786e96b 100644 --- a/src/main/java/envoy/client/data/Cache.java +++ b/src/main/java/envoy/client/data/Cache.java @@ -10,8 +10,8 @@ import java.util.logging.Logger; import envoy.util.EnvoyLog; /** - * Stores elements in a queue to process them later.
- *
+ * Stores elements in a queue to process them later. + *

* Project: envoy-client
* File: Cache.java
* Created: 6 Feb 2020
@@ -40,6 +40,9 @@ public class Cache implements Consumer, Serializable { elements.offer(element); } + @Override + public String toString() { return String.format("Cache[elements=" + elements + "]"); } + /** * Sets the processor to which cached elements are relayed. * diff --git a/src/main/java/envoy/client/data/LocalDB.java b/src/main/java/envoy/client/data/LocalDB.java index b8dc3c9..66407fc 100644 --- a/src/main/java/envoy/client/data/LocalDB.java +++ b/src/main/java/envoy/client/data/LocalDB.java @@ -20,11 +20,11 @@ import envoy.event.NameChange; */ public abstract class LocalDB { - protected User user; - protected Map users = new HashMap<>(); - protected List chats = new ArrayList<>(); - protected IDGenerator idGenerator; - protected Cache messageCache = new Cache<>(); + protected 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<>(); /** @@ -66,6 +66,25 @@ public abstract class LocalDB { */ public void loadIDGenerator() {} + /** + * Synchronizes the contact list of the client user with the chat and user + * storage. + * + * @since Envoy Client v0.1-beta + */ + public void synchronize() { + user.getContacts().stream().filter(u -> u instanceof User && !users.containsKey(u.getName())).forEach(u -> users.put(u.getName(), u)); + users.put(user.getName(), user); + + // Synchronize user status data + for (Contact contact : users.values()) + if (contact instanceof User) + getChat(contact.getID()).ifPresent(chat -> { ((User) chat.getRecipient()).setStatus(((User) contact).getStatus()); }); + + // Create missing chats + user.getContacts().stream().filter(u -> !u.equals(user) && getChat(u.getID()).isEmpty()).map(Chat::new).forEach(chats::add); + } + /** * @return a {@code Map} of all users stored locally with their * user names as keys @@ -73,11 +92,6 @@ public abstract class LocalDB { */ public Map getUsers() { return users; } - /** - * @param users the users to set - */ - public void setUsers(Map users) { this.users = users; } - /** * @return all saved {@link Chat} objects that list the client user as the * sender @@ -154,7 +168,7 @@ public abstract class LocalDB { public Optional getMessage(long id) { return chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny(); } - + /** * Searches for a chat by recipient ID. * @@ -162,9 +176,7 @@ public abstract class LocalDB { * @return an optional containing the chat * @since Envoy Client v0.1-beta */ - public Optional getChat(long recipientID) { - return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); - } + public Optional getChat(long recipientID) { return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); } /** * Performs a contact name change if the corresponding contact is present. @@ -200,13 +212,4 @@ public abstract class LocalDB { } }); } - - /** - * Creates a new {@link Chat} for all {@link Contact}s that do not have a chat. - * - * @since Envoy Client v0.1-beta - */ - public void createMissingChats() { - users.values().stream().filter(u -> !u.equals(user) && getChat(u.getID()).isEmpty()).map(Chat::new).forEach(chats::add); - } } diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java index 39052c1..34384ec 100644 --- a/src/main/java/envoy/client/net/Client.java +++ b/src/main/java/envoy/client/net/Client.java @@ -3,9 +3,6 @@ package envoy.client.net; import java.io.Closeable; import java.io.IOException; import java.net.Socket; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; @@ -42,9 +39,8 @@ public class Client implements Closeable { private boolean online; // Asynchronously initialized during handshake - private volatile User sender; - private volatile Set contacts; - private volatile boolean rejected; + private volatile User sender; + private volatile boolean rejected; // Configuration, logging and event management private static final ClientConfig config = ClientConfig.getInstance(); @@ -73,9 +69,9 @@ public class Client implements Closeable { * waiting for the handshake response */ public void performHandshake(LoginCredentials credentials, Cache receivedMessageCache, - Cache receivedMessageStatusChangeCache) - throws TimeoutException, IOException, InterruptedException { + Cache receivedMessageStatusChangeCache) throws TimeoutException, IOException, InterruptedException { if (online) throw new IllegalStateException("Handshake has already been performed successfully"); + // Establish TCP connection logger.log(Level.FINER, String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort())); socket = new Socket(config.getServer(), config.getPort()); @@ -85,7 +81,7 @@ public class Client implements Closeable { receiver = new Receiver(socket.getInputStream()); // Register user creation processor, contact list processor and message cache - receiver.registerProcessor(User.class, sender -> { this.sender = sender; contacts = sender.getContacts(); }); + receiver.registerProcessor(User.class, sender -> this.sender = sender); receiver.registerProcessor(Message.class, receivedMessageCache); receiver.registerProcessor(MessageStatusChange.class, receivedMessageStatusChangeCache); receiver.registerProcessor(HandshakeRejection.class, evt -> { rejected = true; eventBus.dispatch(evt); }); @@ -142,12 +138,12 @@ public class Client implements Closeable { * requested from the server * @since Envoy Client v0.2-alpha */ - public void initReceiver(LocalDB localDB, Cache receivedMessageCache, - Cache receivedMessageStatusChangeCache) throws IOException { + public void initReceiver(LocalDB localDB, Cache receivedMessageCache, Cache receivedMessageStatusChangeCache) + throws IOException { checkOnline(); // Process incoming messages - final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor(); + final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor(); final MessageStatusChangeProcessor messageStatusChangeEventProcessor = new MessageStatusChangeProcessor(); receiver.registerProcessor(Message.class, receivedMessageProcessor); @@ -182,8 +178,7 @@ public class Client implements Closeable { try { sendEvent(evt.get()); } catch (final IOException e) { - e.printStackTrace(); - logger.log(Level.WARNING, "An error occurred when trying to send Event " + evt, e); + logger.log(Level.WARNING, "An error occurred when trying to send " + evt, e); } }); @@ -234,19 +229,6 @@ public class Client implements Closeable { writeObject(new IDGeneratorRequest()); } - /** - * @return a {@code Map} of all users on the server with their - * user names as keys - * @since Envoy Client v0.2-alpha - */ - public Map getUsers() { - checkOnline(); - final Map users = new HashMap<>(); - contacts.forEach(u -> users.put(u.getName(), u)); - users.put(sender.getName(), sender); - return users; - } - @Override public void close() throws IOException { if (online) socket.close(); } @@ -259,7 +241,7 @@ public class Client implements Closeable { private void checkOnline() { if (!online) throw new IllegalStateException("Client is not online"); } /** - * @return the sender object that represents this client. + * @return the {@link User} as which this client is logged in * @since Envoy Client v0.1-alpha */ public User getSender() { return sender; } @@ -282,16 +264,4 @@ public class Client implements Closeable { * @since Envoy Client v0.2-alpha */ public boolean isOnline() { return online; } - - /** - * @return the contacts of this {@link Client} - * @since Envoy Client v0.3-alpha - */ - public Set getContacts() { return contacts; } - - /** - * @param contacts the contacts to set - * @since Envoy Client v0.3-alpha - */ - public void setContacts(Set contacts) { this.contacts = contacts; } } diff --git a/src/main/java/envoy/client/net/Receiver.java b/src/main/java/envoy/client/net/Receiver.java index 55042d3..c71e5ed 100644 --- a/src/main/java/envoy/client/net/Receiver.java +++ b/src/main/java/envoy/client/net/Receiver.java @@ -79,7 +79,6 @@ public class Receiver extends Thread { // Connection probably closed by client. } catch (final Exception e) { logger.log(Level.SEVERE, "Error on receiver thread", e); - e.printStackTrace(); } } diff --git a/src/main/java/envoy/client/net/WriteProxy.java b/src/main/java/envoy/client/net/WriteProxy.java index 2287369..90e49e0 100644 --- a/src/main/java/envoy/client/net/WriteProxy.java +++ b/src/main/java/envoy/client/net/WriteProxy.java @@ -47,9 +47,6 @@ public class WriteProxy { try { logger.log(Level.FINER, "Sending cached " + msg); client.sendMessage(msg); - - // Update message state to SENT in localDB - localDB.getMessage(msg.getID()).ifPresent(Message::nextStatus); } catch (final IOException e) { logger.log(Level.SEVERE, "Could not send cached message: ", e); } diff --git a/src/main/java/envoy/client/ui/controller/ChatScene.java b/src/main/java/envoy/client/ui/controller/ChatScene.java index cd58ecb..ff93351 100644 --- a/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -164,13 +164,13 @@ public final class ChatScene { private void userListClicked() { final Contact user = userList.getSelectionModel().getSelectedItem(); if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) { + logger.log(Level.FINEST, "Loading chat with " + user); contactLabel.setText(user.getName()); // LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes - // Load the chat or create a new one and add it to the LocalDB - currentChat = localDB.getChat(user.getID()) - .orElseGet(() -> { final var chat = new Chat(user); localDB.getChats().add(chat); return chat; }); + // Load the chat + currentChat = localDB.getChat(user.getID()).get(); messageList.setItems(FXCollections.observableList(currentChat.getMessages())); diff --git a/src/main/java/envoy/client/ui/controller/LoginScene.java b/src/main/java/envoy/client/ui/controller/LoginScene.java index a72ef78..b17fa81 100644 --- a/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -11,9 +11,7 @@ import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; -import envoy.client.data.Cache; -import envoy.client.data.ClientConfig; -import envoy.client.data.LocalDB; +import envoy.client.data.*; import envoy.client.net.Client; import envoy.client.ui.SceneContext; import envoy.client.ui.Startup; @@ -183,7 +181,6 @@ public final class LoginScene { } catch (final FileNotFoundException e) { // The local database file has not yet been created, probably first login } catch (final Exception e) { - e.printStackTrace(); new Alert(AlertType.ERROR, "Error while loading local database: " + e + "\nChats will not be stored locally.").showAndWait(); logger.log(Level.WARNING, "Could not load local database: ", e); } @@ -191,15 +188,18 @@ public final class LoginScene { // Initialize write proxy final var writeProxy = client.createWriteProxy(localDB); - if (client.isOnline()) { + localDB.synchronize(); - // Save all users to the local database and flush cache - localDB.setUsers(client.getUsers()); - localDB.createMissingChats(); + if (client.isOnline()) { writeProxy.flushCache(); } else // Set all contacts to offline mode - localDB.getUsers().values().stream().filter(User.class::isInstance).map(User.class::cast).forEach(u -> u.setStatus(UserStatus.OFFLINE)); + localDB.getChats() + .stream() + .map(Chat::getRecipient) + .filter(User.class::isInstance) + .map(User.class::cast) + .forEach(u -> u.setStatus(UserStatus.OFFLINE)); // Load ChatScene sceneContext.pop();