Merge branch 'develop' into f/enhanced_UI
This commit is contained in:
		| @@ -1,6 +1,6 @@ | |||||||
| # Envoy Client | # Envoy Client | ||||||
|  |  | ||||||
| <a href="https://github.com/informatik-ag-ngl/envoy-client"><img src="https://raw.githubusercontent.com/informatik-ag-ngl/envoy-client/develop/src/main/resources/envoy_logo.png" align="left" width="200" height="200"></a> | <a href="https://github.com/informatik-ag-ngl/envoy-client"><img src="https://raw.githubusercontent.com/informatik-ag-ngl/envoy-client/develop/src/main/resources/icons/envoy_logo.png" align="left" width="200" height="200"></a> | ||||||
|  |  | ||||||
| **Envoy Client** is one of two repositories needed to use the messenger Envoy.<br> | **Envoy Client** is one of two repositories needed to use the messenger Envoy.<br> | ||||||
| The other one is <a href="https://github.com/informatik-ag-ngl/envoy-common">**Envoy Common**</a>. | The other one is <a href="https://github.com/informatik-ag-ngl/envoy-common">**Envoy Common**</a>. | ||||||
|   | |||||||
| @@ -10,8 +10,8 @@ import java.util.logging.Logger; | |||||||
| import envoy.util.EnvoyLog; | import envoy.util.EnvoyLog; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Stores elements in a queue to process them later.<br> |  * Stores elements in a queue to process them later. | ||||||
|  * <br> |  * <p> | ||||||
|  * Project: <strong>envoy-client</strong><br> |  * Project: <strong>envoy-client</strong><br> | ||||||
|  * File: <strong>Cache.java</strong><br> |  * File: <strong>Cache.java</strong><br> | ||||||
|  * Created: <strong>6 Feb 2020</strong><br> |  * Created: <strong>6 Feb 2020</strong><br> | ||||||
| @@ -40,6 +40,9 @@ public class Cache<T> implements Consumer<T>, Serializable { | |||||||
| 		elements.offer(element); | 		elements.offer(element); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public String toString() { return String.format("Cache[elements=" + elements + "]"); } | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Sets the processor to which cached elements are relayed. | 	 * Sets the processor to which cached elements are relayed. | ||||||
| 	 * | 	 * | ||||||
|   | |||||||
| @@ -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 -> 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<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); |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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; } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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(); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -47,9 +47,6 @@ public class WriteProxy { | |||||||
| 			try { | 			try { | ||||||
| 				logger.log(Level.FINER, "Sending cached " + msg); | 				logger.log(Level.FINER, "Sending cached " + msg); | ||||||
| 				client.sendMessage(msg); | 				client.sendMessage(msg); | ||||||
|  |  | ||||||
| 				// Update message state to SENT in localDB |  | ||||||
| 				localDB.getMessage(msg.getID()).ifPresent(Message::nextStatus); |  | ||||||
| 			} catch (final IOException e) { | 			} catch (final IOException e) { | ||||||
| 				logger.log(Level.SEVERE, "Could not send cached message: ", e); | 				logger.log(Level.SEVERE, "Could not send cached message: ", e); | ||||||
| 			} | 			} | ||||||
|   | |||||||
| @@ -176,12 +176,12 @@ public final class ChatScene { | |||||||
| 	private void userListClicked() { | 	private void userListClicked() { | ||||||
| 		final Contact user = userList.getSelectionModel().getSelectedItem(); | 		final Contact user = userList.getSelectionModel().getSelectedItem(); | ||||||
| 		if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) { | 		if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) { | ||||||
|  | 			logger.log(Level.FINEST, "Loading chat with " + user); | ||||||
|  |  | ||||||
| 			// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes | 			// 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())); | ||||||
| 			deleteContactMenuItem.setText("Delete " + user.getName()); | 			deleteContactMenuItem.setText("Delete " + user.getName()); | ||||||
|   | |||||||
| @@ -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(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 delvh
					delvh