Display correct contact status on startup

Fixes #152
This commit is contained in:
Kai S. K. Engelbart 2020-06-26 09:08:41 +02:00
parent 2b59f59006
commit 35f4ca2a1a
5 changed files with 47 additions and 76 deletions

View File

@ -20,11 +20,11 @@ import envoy.event.NameChange;
*/ */
public abstract class LocalDB { public abstract class LocalDB {
protected User user; protected User user;
protected Map<String, Contact> users = new HashMap<>(); protected Map<String, Contact> users = new HashMap<>();
protected List<Chat> chats = new ArrayList<>(); protected List<Chat> chats = new ArrayList<>();
protected IDGenerator idGenerator; protected IDGenerator idGenerator;
protected Cache<Message> messageCache = new Cache<>(); protected Cache<Message> messageCache = new Cache<>();
protected Cache<MessageStatusChange> statusCache = new Cache<>(); protected Cache<MessageStatusChange> statusCache = new Cache<>();
/** /**
@ -66,6 +66,25 @@ public abstract class LocalDB {
*/ */
public void loadIDGenerator() {} 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 -> !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
users.values().stream().filter(u -> !u.equals(user) && getChat(u.getID()).isEmpty()).map(Chat::new).forEach(chats::add);
}
/** /**
* @return a {@code Map<String, User>} of all users stored locally with their * @return a {@code Map<String, User>} of all users stored locally with their
* user names as keys * user names as keys
@ -73,11 +92,6 @@ public abstract class LocalDB {
*/ */
public Map<String, Contact> getUsers() { return users; } public Map<String, Contact> getUsers() { return users; }
/**
* @param users the users to set
*/
public void setUsers(Map<String, Contact> users) { this.users = users; }
/** /**
* @return all saved {@link Chat} objects that list the client user as the * @return all saved {@link Chat} objects that list the client user as the
* sender * sender
@ -154,7 +168,7 @@ public abstract class LocalDB {
public Optional<Message> getMessage(long id) { public Optional<Message> getMessage(long id) {
return chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny(); return chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny();
} }
/** /**
* Searches for a chat by recipient ID. * Searches for a chat by recipient ID.
* *
@ -162,9 +176,7 @@ public abstract class LocalDB {
* @return an optional containing the chat * @return an optional containing the chat
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public Optional<Chat> getChat(long recipientID) { public Optional<Chat> getChat(long recipientID) { return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); }
return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny();
}
/** /**
* Performs a contact name change if the corresponding contact is present. * 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);
}
} }

View File

@ -3,9 +3,6 @@ package envoy.client.net;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -42,9 +39,8 @@ public class Client implements Closeable {
private boolean online; private boolean online;
// Asynchronously initialized during handshake // Asynchronously initialized during handshake
private volatile User sender; private volatile User sender;
private volatile Set<? extends Contact> contacts; private volatile boolean rejected;
private volatile boolean rejected;
// Configuration, logging and event management // Configuration, logging and event management
private static final ClientConfig config = ClientConfig.getInstance(); private static final ClientConfig config = ClientConfig.getInstance();
@ -73,9 +69,9 @@ public class Client implements Closeable {
* waiting for the handshake response * waiting for the handshake response
*/ */
public void performHandshake(LoginCredentials credentials, Cache<Message> receivedMessageCache, public void performHandshake(LoginCredentials credentials, Cache<Message> receivedMessageCache,
Cache<MessageStatusChange> receivedMessageStatusChangeCache) Cache<MessageStatusChange> receivedMessageStatusChangeCache) throws TimeoutException, IOException, InterruptedException {
throws TimeoutException, IOException, InterruptedException {
if (online) throw new IllegalStateException("Handshake has already been performed successfully"); if (online) throw new IllegalStateException("Handshake has already been performed successfully");
// Establish TCP connection // Establish TCP connection
logger.log(Level.FINER, String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort())); logger.log(Level.FINER, String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
socket = new Socket(config.getServer(), config.getPort()); socket = new Socket(config.getServer(), config.getPort());
@ -85,7 +81,7 @@ public class Client implements Closeable {
receiver = new Receiver(socket.getInputStream()); receiver = new Receiver(socket.getInputStream());
// Register user creation processor, contact list processor and message cache // 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(Message.class, receivedMessageCache);
receiver.registerProcessor(MessageStatusChange.class, receivedMessageStatusChangeCache); receiver.registerProcessor(MessageStatusChange.class, receivedMessageStatusChangeCache);
receiver.registerProcessor(HandshakeRejection.class, evt -> { rejected = true; eventBus.dispatch(evt); }); receiver.registerProcessor(HandshakeRejection.class, evt -> { rejected = true; eventBus.dispatch(evt); });
@ -142,12 +138,12 @@ public class Client implements Closeable {
* requested from the server * requested from the server
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public void initReceiver(LocalDB localDB, Cache<Message> receivedMessageCache, public void initReceiver(LocalDB localDB, Cache<Message> receivedMessageCache, Cache<MessageStatusChange> receivedMessageStatusChangeCache)
Cache<MessageStatusChange> receivedMessageStatusChangeCache) throws IOException { throws IOException {
checkOnline(); checkOnline();
// Process incoming messages // Process incoming messages
final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor(); final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor();
final MessageStatusChangeProcessor messageStatusChangeEventProcessor = new MessageStatusChangeProcessor(); final MessageStatusChangeProcessor messageStatusChangeEventProcessor = new MessageStatusChangeProcessor();
receiver.registerProcessor(Message.class, receivedMessageProcessor); receiver.registerProcessor(Message.class, receivedMessageProcessor);
@ -182,8 +178,7 @@ public class Client implements Closeable {
try { try {
sendEvent(evt.get()); sendEvent(evt.get());
} catch (final IOException e) { } catch (final IOException e) {
e.printStackTrace(); logger.log(Level.WARNING, "An error occurred when trying to send " + evt, e);
logger.log(Level.WARNING, "An error occurred when trying to send Event " + evt, e);
} }
}); });
@ -234,19 +229,6 @@ public class Client implements Closeable {
writeObject(new IDGeneratorRequest()); writeObject(new IDGeneratorRequest());
} }
/**
* @return a {@code Map<String, User>} of all users on the server with their
* user names as keys
* @since Envoy Client v0.2-alpha
*/
public Map<String, Contact> getUsers() {
checkOnline();
final Map<String, Contact> users = new HashMap<>();
contacts.forEach(u -> users.put(u.getName(), u));
users.put(sender.getName(), sender);
return users;
}
@Override @Override
public void close() throws IOException { if (online) socket.close(); } 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"); } 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 * @since Envoy Client v0.1-alpha
*/ */
public User getSender() { return sender; } public User getSender() { return sender; }
@ -282,16 +264,4 @@ public class Client implements Closeable {
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
public boolean isOnline() { return online; } public boolean isOnline() { return online; }
/**
* @return the contacts of this {@link Client}
* @since Envoy Client v0.3-alpha
*/
public Set<? extends Contact> getContacts() { return contacts; }
/**
* @param contacts the contacts to set
* @since Envoy Client v0.3-alpha
*/
public void setContacts(Set<? extends Contact> contacts) { this.contacts = contacts; }
} }

View File

@ -79,7 +79,6 @@ public class Receiver extends Thread {
// Connection probably closed by client. // Connection probably closed by client.
} catch (final Exception e) { } catch (final Exception e) {
logger.log(Level.SEVERE, "Error on receiver thread", e); logger.log(Level.SEVERE, "Error on receiver thread", e);
e.printStackTrace();
} }
} }

View File

@ -168,9 +168,8 @@ public final class ChatScene {
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes // LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
// Load the chat or create a new one and add it to the LocalDB // Load the chat
currentChat = localDB.getChat(user.getID()) currentChat = localDB.getChat(user.getID()).get();
.orElseGet(() -> { final var chat = new Chat(user); localDB.getChats().add(chat); return chat; });
messageList.setItems(FXCollections.observableList(currentChat.getMessages())); messageList.setItems(FXCollections.observableList(currentChat.getMessages()));

View File

@ -11,9 +11,7 @@ import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import envoy.client.data.Cache; import envoy.client.data.*;
import envoy.client.data.ClientConfig;
import envoy.client.data.LocalDB;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.ui.SceneContext; import envoy.client.ui.SceneContext;
import envoy.client.ui.Startup; import envoy.client.ui.Startup;
@ -183,7 +181,6 @@ public final class LoginScene {
} catch (final FileNotFoundException e) { } catch (final FileNotFoundException e) {
// The local database file has not yet been created, probably first login // The local database file has not yet been created, probably first login
} catch (final Exception e) { } catch (final Exception e) {
e.printStackTrace();
new Alert(AlertType.ERROR, "Error while loading local database: " + e + "\nChats will not be stored locally.").showAndWait(); 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); logger.log(Level.WARNING, "Could not load local database: ", e);
} }
@ -191,15 +188,18 @@ public final class LoginScene {
// Initialize write proxy // Initialize write proxy
final var writeProxy = client.createWriteProxy(localDB); final var writeProxy = client.createWriteProxy(localDB);
if (client.isOnline()) { localDB.synchronize();
// Save all users to the local database and flush cache if (client.isOnline()) {
localDB.setUsers(client.getUsers());
localDB.createMissingChats();
writeProxy.flushCache(); writeProxy.flushCache();
} else } else
// Set all contacts to offline mode // 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 // Load ChatScene
sceneContext.pop(); sceneContext.pop();