Merge branch 'develop' into f/new_ui
This commit is contained in:
		
							
								
								
									
										4
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/ISSUE_TEMPLATE/bug_report.md
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +1,7 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
name: Bug report
 | 
					name: Bug report
 | 
				
			||||||
about: Create a report to help us improve
 | 
					about: Create a report to help us improve
 | 
				
			||||||
title: ''
 | 
					 | 
				
			||||||
labels: bug
 | 
					labels: bug
 | 
				
			||||||
assignees: CyB3RC0nN0R, delvh, DieGurke, derharry333
 | 
					 | 
				
			||||||
projects: Envoy
 | 
					 | 
				
			||||||
milestone: Envoy v0.2-beta
 | 
					 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Describe the bug**
 | 
					**Describe the bug**
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/ISSUE_TEMPLATE/feature_request.md
									
									
									
									
										vendored
									
									
								
							@@ -1,11 +1,7 @@
 | 
				
			|||||||
---
 | 
					---
 | 
				
			||||||
name: Feature request
 | 
					name: Feature request
 | 
				
			||||||
about: Suggest an idea for this project
 | 
					about: Suggest an idea for this project
 | 
				
			||||||
title: ''
 | 
					labels: enhancement
 | 
				
			||||||
labels: enhancement, feature
 | 
					 | 
				
			||||||
assignees: CyB3RC0nN0R, delvh, DieGurke
 | 
					 | 
				
			||||||
project: Envoy
 | 
					 | 
				
			||||||
milestones: Envoy v0.2-beta
 | 
					 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Is your feature request related to a problem? Please describe.**
 | 
					**Is your feature request related to a problem? Please describe.**
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								.github/workflows/maven.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/maven.yml
									
									
									
									
										vendored
									
									
								
							@@ -24,9 +24,12 @@ jobs:
 | 
				
			|||||||
        restore-keys: ${{ runner.os }}-m2
 | 
					        restore-keys: ${{ runner.os }}-m2
 | 
				
			||||||
    - name: Build with Maven
 | 
					    - name: Build with Maven
 | 
				
			||||||
      run: mvn -B package
 | 
					      run: mvn -B package
 | 
				
			||||||
 | 
					    - name: Stage build artifacts
 | 
				
			||||||
 | 
					      run: |
 | 
				
			||||||
 | 
					        mkdir staging
 | 
				
			||||||
 | 
					        cp server/target/envoy-server-jar-with-dependencies.jar staging/envoy-server.jar
 | 
				
			||||||
 | 
					        cp client/target/envoy-client*shaded.jar staging/envoy.jar
 | 
				
			||||||
    - uses: actions/upload-artifact@v2
 | 
					    - uses: actions/upload-artifact@v2
 | 
				
			||||||
      with:
 | 
					      with:
 | 
				
			||||||
        name: envoy-${{ matrix.os }}
 | 
					        name: envoy-${{ matrix.os }}
 | 
				
			||||||
        path: |
 | 
					        path: staging
 | 
				
			||||||
          server/target/envoy-server-jar-with-dependencies.jar
 | 
					 | 
				
			||||||
          client/target/envoy-client*shaded.jar
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										49
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					# Envoy
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					<a href="https://github.com/informatik-ag-ngl/envoy"><img src="https://raw.githubusercontent.com/informatik-ag-ngl/envoy/develop/client/src/main/resources/icons/envoy_logo.png" align="right" width="150" height="150"></a>
 | 
				
			||||||
 | 
					<a href="https://github.com/informatik-ag-ngl/envoy/milestone/1"><img alt="GitHub milestone" src="https://img.shields.io/github/milestones/progress-percent/informatik-ag-ngl/envoy/1"></a>
 | 
				
			||||||
 | 
					<a href="https://github.com/informatik-ag-ngl/envoy/pulls"><img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr-raw/informatik-ag-ngl/envoy?style=flat"></a>
 | 
				
			||||||
 | 
					<a href="https://github.com/informatik-ag-ngl/envoy/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues/informatik-ag-ngl/envoy?style=flat"></a>
 | 
				
			||||||
 | 
					<a href="https://github.com/informatik-ag-ngl/envoy/actions"><img src="https://github.com/informatik-ag-ngl/envoy/workflows/Java%20CI/badge.svg"></a>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Envoy** is a messenger written in Java.<br>
 | 
				
			||||||
 | 
					It is split into three separate components: Envoy Client, Envoy Common and Envoy Server.
 | 
				
			||||||
 | 
					<br><br><br><br><br>
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					### Envoy Client:
 | 
				
			||||||
 | 
					This is the only part users are interested in. It contains everything to make this messenger work: the UI.
 | 
				
			||||||
 | 
					### Envoy Server:
 | 
				
			||||||
 | 
					Envoy offers the option to download and host your own server over which Envoy can run.<br>
 | 
				
			||||||
 | 
					This part will be especially appealing to institutions/organizations who want to self-host Envoy.
 | 
				
			||||||
 | 
					### Envoy Common:
 | 
				
			||||||
 | 
					This part contains elements that both the client and the server need. It will be automatically part of either one (Thanks, Maven!).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Envoy features a lot of things and many more are yet to come.
 | 
				
			||||||
 | 
					Currently existing features are:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 'Client' contains:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* typical Messenger features (sending and receiving of messages, groups, sending images and voice messages)
 | 
				
			||||||
 | 
					* typical Messenger feeling (displaying unread messages)
 | 
				
			||||||
 | 
					* Appealing user interface (UI)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* Programming
 | 
				
			||||||
 | 
					  * API to change default configuration
 | 
				
			||||||
 | 
					  * Advanced logging possibilities
 | 
				
			||||||
 | 
					  * Tons of Events to interact with
 | 
				
			||||||
 | 
					  * Detailed Javadoc to improve readability of code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 'Common' contains:
 | 
				
			||||||
 | 
					* the event system
 | 
				
			||||||
 | 
					* the logger
 | 
				
			||||||
 | 
					* Envoy-specific Exceptions
 | 
				
			||||||
 | 
					* some util classes
 | 
				
			||||||
 | 
					* the most basic datatypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### 'Server' contains:
 | 
				
			||||||
 | 
					* the database implementation of the data classes
 | 
				
			||||||
 | 
					* the connectivity classes
 | 
				
			||||||
 | 
					* processors to handle incoming events
 | 
				
			||||||
 | 
					* Utility classes to check client version compatability and Password validity
 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -37,15 +37,6 @@
 | 
				
			|||||||
				<directory>src/main/resources</directory>
 | 
									<directory>src/main/resources</directory>
 | 
				
			||||||
			</resource>
 | 
								</resource>
 | 
				
			||||||
		</resources>
 | 
							</resources>
 | 
				
			||||||
		<pluginManagement>
 | 
					 | 
				
			||||||
			<plugins>
 | 
					 | 
				
			||||||
				<plugin>
 | 
					 | 
				
			||||||
					<groupId>org.apache.maven.plugins</groupId>
 | 
					 | 
				
			||||||
					<artifactId>maven-compiler-plugin</artifactId>
 | 
					 | 
				
			||||||
					<version>3.8.1</version>
 | 
					 | 
				
			||||||
				</plugin>
 | 
					 | 
				
			||||||
			</plugins>
 | 
					 | 
				
			||||||
		</pluginManagement>
 | 
					 | 
				
			||||||
		<plugins>
 | 
							<plugins>
 | 
				
			||||||
			<plugin>
 | 
								<plugin>
 | 
				
			||||||
				<groupId>org.apache.maven.plugins</groupId>
 | 
									<groupId>org.apache.maven.plugins</groupId>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,8 +7,10 @@ import java.util.List;
 | 
				
			|||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.net.WriteProxy;
 | 
					import envoy.client.net.WriteProxy;
 | 
				
			||||||
import envoy.data.*;
 | 
					import envoy.data.Contact;
 | 
				
			||||||
 | 
					import envoy.data.Message;
 | 
				
			||||||
import envoy.data.Message.MessageStatus;
 | 
					import envoy.data.Message.MessageStatus;
 | 
				
			||||||
 | 
					import envoy.data.User;
 | 
				
			||||||
import envoy.event.MessageStatusChange;
 | 
					import envoy.event.MessageStatusChange;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -31,6 +33,11 @@ public class Chat implements Serializable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	protected int unreadAmount;
 | 
						protected int unreadAmount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Stores the last time an {@link envoy.event.IsTyping} event has been sent.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected transient long lastWritingEvent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final long serialVersionUID = 1L;
 | 
						private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -41,9 +48,7 @@ public class Chat implements Serializable {
 | 
				
			|||||||
	 * @param recipient the user who receives the messages
 | 
						 * @param recipient the user who receives the messages
 | 
				
			||||||
	 * @since Envoy Client v0.1-alpha
 | 
						 * @since Envoy Client v0.1-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Chat(Contact recipient) {
 | 
						public Chat(Contact recipient) { this.recipient = recipient; }
 | 
				
			||||||
		this.recipient	= recipient;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() { return String.format("Chat[recipient=%s,messages=%d]", recipient, messages.size()); }
 | 
						public String toString() { return String.format("Chat[recipient=%s,messages=%d]", recipient, messages.size()); }
 | 
				
			||||||
@@ -65,7 +70,7 @@ public class Chat implements Serializable {
 | 
				
			|||||||
	public boolean equals(Object obj) {
 | 
						public boolean equals(Object obj) {
 | 
				
			||||||
		if (this == obj) return true;
 | 
							if (this == obj) return true;
 | 
				
			||||||
		if (!(obj instanceof Chat)) return false;
 | 
							if (!(obj instanceof Chat)) return false;
 | 
				
			||||||
		Chat other = (Chat) obj;
 | 
							final Chat other = (Chat) obj;
 | 
				
			||||||
		return Objects.equals(recipient, other.recipient);
 | 
							return Objects.equals(recipient, other.recipient);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,7 +127,7 @@ public class Chat implements Serializable {
 | 
				
			|||||||
	public void incrementUnreadAmount() { unreadAmount++; }
 | 
						public void incrementUnreadAmount() { unreadAmount++; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the amount of unread mesages in this chat
 | 
						 * @return the amount of unread messages in this chat
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public int getUnreadAmount() { return unreadAmount; }
 | 
						public int getUnreadAmount() { return unreadAmount; }
 | 
				
			||||||
@@ -140,14 +145,16 @@ public class Chat implements Serializable {
 | 
				
			|||||||
	public Contact getRecipient() { return recipient; }
 | 
						public Contact getRecipient() { return recipient; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return whether this {@link Chat} points at a {@link User}
 | 
						 * @return the last known time a {@link envoy.event.IsTyping} event has been
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 *         sent
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean isUserChat() { return recipient instanceof User; }
 | 
						public long getLastWritingEvent() { return lastWritingEvent; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return whether this {@link Chat} points at a {@link Group}
 | 
						 * Sets the {@code lastWritingEvent} to {@code System#currentTimeMillis()}.
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 *
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean isGroupChat() { return recipient instanceof Group; }
 | 
						public void lastWritingEventWasNow() { lastWritingEvent = System.currentTimeMillis(); }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -75,8 +75,12 @@ public class Settings {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private void supplementDefaults() {
 | 
						private void supplementDefaults() {
 | 
				
			||||||
		items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key."));
 | 
							items.putIfAbsent("enterToSend", new SettingsItem<>(true, "Enter to send", "Sends a message by pressing the enter key."));
 | 
				
			||||||
		items.putIfAbsent("onCloseMode", new SettingsItem<>(true, "Hide on close", "Hides the chat window when it is closed."));
 | 
							items.putIfAbsent("hideOnClose", new SettingsItem<>(true, "Hide on close", "Hides the chat window when it is closed."));
 | 
				
			||||||
		items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name", "The name of the currently selected theme."));
 | 
							items.putIfAbsent("currentTheme", new SettingsItem<>("dark", "Current Theme Name", "The name of the currently selected theme."));
 | 
				
			||||||
 | 
							items.putIfAbsent("downloadLocation",
 | 
				
			||||||
 | 
									new SettingsItem<>(new File(System.getProperty("user.home") + "/Downloads/"), "Download location",
 | 
				
			||||||
 | 
											"The location where files will be saved to"));
 | 
				
			||||||
 | 
							items.putIfAbsent("autoSaveDownloads", new SettingsItem<>(false, "Save without asking?", "Should downloads be saved without asking?"));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -120,19 +124,50 @@ public class Settings {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setEnterToSend(boolean enterToSend) { ((SettingsItem<Boolean>) items.get("enterToSend")).set(enterToSend); }
 | 
						public void setEnterToSend(boolean enterToSend) { ((SettingsItem<Boolean>) items.get("enterToSend")).set(enterToSend); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return whether Envoy will prompt a dialogue before saving an
 | 
				
			||||||
 | 
						 *         {@link envoy.data.Attachment}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Boolean isDownloadSavedWithoutAsking() { return (Boolean) items.get("autoSaveDownloads").get(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Sets whether Envoy will prompt a dialogue before saving an
 | 
				
			||||||
 | 
						 * {@link envoy.data.Attachment}.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param autosaveDownload whether a download should be saved without asking
 | 
				
			||||||
 | 
						 *                         before
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void setDownloadSavedWithoutAsking(boolean autosaveDownload) { ((SettingsItem<Boolean>) items.get("autoSaveDownloads")).set(autosaveDownload); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the path where downloads should be saved
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public File getDownloadLocation() { return (File) items.get("downloadLocation").get(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Sets the path where downloads should be saved.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param downloadLocation the path to set
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void setDownloadLocation(File downloadLocation) { ((SettingsItem<File>) items.get("downloadLocation")).set(downloadLocation); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the current on close mode.
 | 
						 * @return the current on close mode.
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Boolean getCurrentOnCloseMode() { return (Boolean) items.get("onCloseMode").get(); }
 | 
						public Boolean isHideOnClose() { return (Boolean) items.get("hideOnClose").get(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Sets the current on close mode.
 | 
						 * Sets the current on close mode.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param currentOnCloseMode the on close mode that should be set.
 | 
						 * @param hideOnClose whether the application should be minimized on close
 | 
				
			||||||
	 * @since Envoy Client v0.3-alpha
 | 
						 * @since Envoy Client v0.3-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setCurrentOnCloseMode(boolean currentOnCloseMode) { ((SettingsItem<Boolean>) items.get("onCloseMode")).set(currentOnCloseMode); }
 | 
						public void setHideOnClose(boolean hideOnClose) { ((SettingsItem<Boolean>) items.get("hideOnClose")).set(hideOnClose); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return the items
 | 
						 * @return the items
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,6 +27,11 @@ public final class AudioRecorder {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static final AudioFormat DEFAULT_AUDIO_FORMAT = new AudioFormat(16000, 16, 1, true, false);
 | 
						public static final AudioFormat DEFAULT_AUDIO_FORMAT = new AudioFormat(16000, 16, 1, true, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * The format in which audio files will be saved.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static final String FILE_FORMAT = "wav";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final AudioFormat	format;
 | 
						private final AudioFormat	format;
 | 
				
			||||||
	private final DataLine.Info	info;
 | 
						private final DataLine.Info	info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,7 +83,7 @@ public final class AudioRecorder {
 | 
				
			|||||||
			line.start();
 | 
								line.start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Prepare temp file
 | 
								// Prepare temp file
 | 
				
			||||||
			tempFile = Files.createTempFile("recording", "wav");
 | 
								tempFile = Files.createTempFile("recording", FILE_FORMAT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Start the recording
 | 
								// Start the recording
 | 
				
			||||||
			final var ais = new AudioInputStream(line);
 | 
								final var ais = new AudioInputStream(line);
 | 
				
			||||||
@@ -117,6 +122,6 @@ public final class AudioRecorder {
 | 
				
			|||||||
		line.close();
 | 
							line.close();
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			Files.deleteIfExists(tempFile);
 | 
								Files.deleteIfExists(tempFile);
 | 
				
			||||||
		} catch (IOException e) {}
 | 
							} catch (final IOException e) {}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										34
									
								
								client/src/main/java/envoy/client/data/commands/OnCall.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								client/src/main/java/envoy/client/data/commands/OnCall.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					package envoy.client.data.commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.function.Supplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This interface defines an action that should be performed when a system
 | 
				
			||||||
 | 
					 * command gets called.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>OnCall.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>23.07.2020</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public interface OnCall {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Performs class specific actions when a {@link SystemCommand} has been called.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						void onCall();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Performs actions that can only be performed by classes that are not
 | 
				
			||||||
 | 
						 * {@link SystemCommand}s when a SystemCommand has been called.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param consumer the action to perform when this {@link SystemCommand} has
 | 
				
			||||||
 | 
						 *                 been called
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						void onCall(Supplier<Void> consumer);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					package envoy.client.data.commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
 | 
					import java.util.function.Supplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This class is the base class of all {@code SystemCommands} and contains an
 | 
				
			||||||
 | 
					 * action and a number of arguments that should be used as input for this
 | 
				
			||||||
 | 
					 * function.
 | 
				
			||||||
 | 
					 * No {@code SystemCommand} can return anything.
 | 
				
			||||||
 | 
					 * Every {@code SystemCommand} must have as argument type {@code List<String>} so
 | 
				
			||||||
 | 
					 * that the words following the indicator String can be used as input of the
 | 
				
			||||||
 | 
					 * function. This approach has one limitation:<br>
 | 
				
			||||||
 | 
					 * <b>Order matters!</b> Changing the order of arguments will likely result in
 | 
				
			||||||
 | 
					 * unexpected behavior.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>SystemCommand.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>16.07.2020</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class SystemCommand implements OnCall {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected int relevance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * The argument count of the command.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected final int numberOfArguments;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * This function takes a {@code List<String>} as argument because automatically
 | 
				
			||||||
 | 
						 * {@code SystemCommand#numberOfArguments} words following the necessary command
 | 
				
			||||||
 | 
						 * will be put into this list.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @see String#split(String)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						protected final Consumer<List<String>> action;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected final String description;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected final List<String> defaults;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Constructs a new {@code SystemCommand}.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param action            the action performed by the command
 | 
				
			||||||
 | 
						 * @param numberOfArguments the argument count accepted by the action
 | 
				
			||||||
 | 
						 * @param defaults          the default values for the corresponding arguments
 | 
				
			||||||
 | 
						 * @param description       the description of this {@code SystemCommand}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public SystemCommand(Consumer<List<String>> action, int numberOfArguments, List<String> defaults, String description) {
 | 
				
			||||||
 | 
							this.numberOfArguments	= numberOfArguments;
 | 
				
			||||||
 | 
							this.action				= action;
 | 
				
			||||||
 | 
							this.defaults			= defaults == null ? new ArrayList<>() : defaults;
 | 
				
			||||||
 | 
							this.description		= description;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the action that should be performed
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Consumer<List<String>> getAction() { return action; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the argument count of the command
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public int getNumberOfArguments() { return numberOfArguments; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the description
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public String getDescription() { return description; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the relevance
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public int getRelevance() { return relevance; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param relevance the relevance to set
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void setRelevance(int relevance) { this.relevance = relevance; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Increments the relevance of this {@code SystemCommand}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void onCall() { relevance++; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Increments the relevance of this {@code SystemCommand} and executes the
 | 
				
			||||||
 | 
						 * supplier.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void onCall(Supplier<Void> consumer) {
 | 
				
			||||||
 | 
							onCall();
 | 
				
			||||||
 | 
							consumer.get();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the defaults
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public List<String> getDefaults() { return defaults; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public int hashCode() { return Objects.hash(action); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public boolean equals(Object obj) {
 | 
				
			||||||
 | 
							if (this == obj) return true;
 | 
				
			||||||
 | 
							if (obj == null) return false;
 | 
				
			||||||
 | 
							if (getClass() != obj.getClass()) return false;
 | 
				
			||||||
 | 
							final SystemCommand other = (SystemCommand) obj;
 | 
				
			||||||
 | 
							return Objects.equals(action, other.action);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return "SystemCommand [relevance=" + relevance + ", numberOfArguments=" + numberOfArguments + ", "
 | 
				
			||||||
 | 
									+ (action != null ? "action=" + action + ", " : "") + (description != null ? "description=" + description + ", " : "")
 | 
				
			||||||
 | 
									+ (defaults != null ? "defaults=" + defaults : "") + "]";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,144 @@
 | 
				
			|||||||
 | 
					package envoy.client.data.commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This class acts as a builder for {@link SystemCommand}s.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>SystemCommandBuilder.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>23.07.2020</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class SystemCommandBuilder {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private int						numberOfArguments;
 | 
				
			||||||
 | 
						private Consumer<List<String>>	action;
 | 
				
			||||||
 | 
						private List<String>			defaults;
 | 
				
			||||||
 | 
						private String					description;
 | 
				
			||||||
 | 
						private int						relevance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param numberOfArguments the numberOfArguments to set
 | 
				
			||||||
 | 
						 * @return this {@code SystemCommandBuilder}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public SystemCommandBuilder setNumberOfArguments(int numberOfArguments) {
 | 
				
			||||||
 | 
							this.numberOfArguments = numberOfArguments;
 | 
				
			||||||
 | 
							return this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param action the action to set
 | 
				
			||||||
 | 
						 * @return this {@code SystemCommandBuilder}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public SystemCommandBuilder setAction(Consumer<List<String>> action) {
 | 
				
			||||||
 | 
							this.action = action;
 | 
				
			||||||
 | 
							return this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param description the description to set
 | 
				
			||||||
 | 
						 * @return this {@code SystemCommandBuilder}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public SystemCommandBuilder setDescription(String description) {
 | 
				
			||||||
 | 
							this.description = description;
 | 
				
			||||||
 | 
							return this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param relevance the relevance to set
 | 
				
			||||||
 | 
						 * @return this {@code SystemCommandBuilder}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public SystemCommandBuilder setRelevance(int relevance) {
 | 
				
			||||||
 | 
							this.relevance = relevance;
 | 
				
			||||||
 | 
							return this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param defaults the defaults to set
 | 
				
			||||||
 | 
						 * @return this {@code SystemCommandBuilder}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public SystemCommandBuilder setDefaults(String... defaults) {
 | 
				
			||||||
 | 
							this.defaults = List.of(defaults);
 | 
				
			||||||
 | 
							return this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Resets all values stored.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @return this {@code SystemCommandBuilder}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public SystemCommandBuilder reset() {
 | 
				
			||||||
 | 
							numberOfArguments	= 0;
 | 
				
			||||||
 | 
							action				= null;
 | 
				
			||||||
 | 
							defaults			= new ArrayList<>();
 | 
				
			||||||
 | 
							description			= "";
 | 
				
			||||||
 | 
							relevance			= 0;
 | 
				
			||||||
 | 
							return this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Builds a {@code SystemCommand} based upon the previously entered data.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @return the built {@code SystemCommand}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public SystemCommand build() { return build(true); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Builds a {@code SystemCommand} based upon the previously entered data.<br>
 | 
				
			||||||
 | 
						 * {@code SystemCommand#numberOfArguments} will be set to 0, regardless of the
 | 
				
			||||||
 | 
						 * previous value.<br>
 | 
				
			||||||
 | 
						 * At the end, this {@code SystemCommandBuilder} will be reset.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @return the built {@code SystemCommand}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public SystemCommand buildNoArg() {
 | 
				
			||||||
 | 
							numberOfArguments = 0;
 | 
				
			||||||
 | 
							return build(true);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Builds a {@code SystemCommand} based upon the previously entered data.<br>
 | 
				
			||||||
 | 
						 * {@code SystemCommand#numberOfArguments} will be set to use the rest of the
 | 
				
			||||||
 | 
						 * string as argument, regardless of the previous value.<br>
 | 
				
			||||||
 | 
						 * At the end, this {@code SystemCommandBuilder} will be reset.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @return the built {@code SystemCommand}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public SystemCommand buildRemainingArg() {
 | 
				
			||||||
 | 
							numberOfArguments = -1;
 | 
				
			||||||
 | 
							return build(true);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Builds a {@code SystemCommand} based upon the previously entered data.<br>
 | 
				
			||||||
 | 
						 * At the end, this {@code SystemCommandBuilder} <b>can</b> be reset but must
 | 
				
			||||||
 | 
						 * not be.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param reset whether this {@code SystemCommandBuilder} should be reset
 | 
				
			||||||
 | 
						 *              afterwards.<br>
 | 
				
			||||||
 | 
						 *              This can be useful if another command wants to execute something
 | 
				
			||||||
 | 
						 *              similar
 | 
				
			||||||
 | 
						 * @return the built {@code SystemCommand}
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public SystemCommand build(boolean reset) {
 | 
				
			||||||
 | 
							final var sc = new SystemCommand(action, numberOfArguments, defaults, description);
 | 
				
			||||||
 | 
							sc.setRelevance(relevance);
 | 
				
			||||||
 | 
							if (reset) reset();
 | 
				
			||||||
 | 
							return sc;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,263 @@
 | 
				
			|||||||
 | 
					package envoy.client.data.commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.*;
 | 
				
			||||||
 | 
					import java.util.function.Function;
 | 
				
			||||||
 | 
					import java.util.logging.Level;
 | 
				
			||||||
 | 
					import java.util.logging.Logger;
 | 
				
			||||||
 | 
					import java.util.regex.Pattern;
 | 
				
			||||||
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This class stores all {@link SystemCommand}s used.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>SystemCommandsMap.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>17.07.2020</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public final class SystemCommandsMap {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Map<String, SystemCommand> systemCommands = new HashMap<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Pattern commandPattern = Pattern.compile("^[a-zA-Z0-9_:!\\(\\)\\?\\.\\,\\;\\-]+$");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final Logger logger = EnvoyLog.getLogger(SystemCommandsMap.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Adds a new command to the map if the command name is valid.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param command       the input string to execute the
 | 
				
			||||||
 | 
						 *                      given action
 | 
				
			||||||
 | 
						 * @param systemCommand the command to add - can be built using
 | 
				
			||||||
 | 
						 *                      {@link SystemCommandBuilder}
 | 
				
			||||||
 | 
						 * @see SystemCommandsMap#isValidKey(String)
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void add(String command, SystemCommand systemCommand) { if (isValidKey(command)) systemCommands.put(command, systemCommand); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * This method checks if the input String is a key in the map and returns the
 | 
				
			||||||
 | 
						 * wrapped System command if present.
 | 
				
			||||||
 | 
						 * It will return an empty optional if the value after the slash is not a key in
 | 
				
			||||||
 | 
						 * the map, which is a valid case (i.e. input="3/4" and "4" is not a key in the
 | 
				
			||||||
 | 
						 * map).
 | 
				
			||||||
 | 
						 * <p>
 | 
				
			||||||
 | 
						 * Usage example:<br>
 | 
				
			||||||
 | 
						 * {@code SystemCommandsMap systemCommands = new SystemCommandsMap();}<br>
 | 
				
			||||||
 | 
						 * {@code Button button = new  Button();}
 | 
				
			||||||
 | 
						 * {@code systemCommands.add("example", text -> button.setText(text.get(0), 1);}<br>
 | 
				
			||||||
 | 
						 * {@code ....}<br>
 | 
				
			||||||
 | 
						 * user input: {@code "/example xyz ..."}<br>
 | 
				
			||||||
 | 
						 * {@code systemCommands.get("example xyz ...")} or
 | 
				
			||||||
 | 
						 * {@code systemCommands.get("/example xyz ...")}
 | 
				
			||||||
 | 
						 * result: {@code Optional<SystemCommand>}
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param input the input string given by the user
 | 
				
			||||||
 | 
						 * @return the wrapped system command, if present
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Optional<SystemCommand> get(String input) { return Optional.ofNullable(systemCommands.get(getCommand(input))); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * This method ensures that the "/" of a {@link SystemCommand} is stripped.<br>
 | 
				
			||||||
 | 
						 * It returns the command as (most likely) entered as key in the map for the
 | 
				
			||||||
 | 
						 * first word of the text.<br>
 | 
				
			||||||
 | 
						 * It should only be called on strings that contain a "/" at position 0/-1.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param raw the input
 | 
				
			||||||
 | 
						 * @return the command as entered in the map
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 * @apiNote this method will (most likely) not return anything useful if
 | 
				
			||||||
 | 
						 *          whatever is entered after the slash is not a system command. Only
 | 
				
			||||||
 | 
						 *          exception: for recommendation purposes.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public String getCommand(String raw) {
 | 
				
			||||||
 | 
							final var	trimmed	= raw.stripLeading();
 | 
				
			||||||
 | 
							final var	index	= trimmed.indexOf(' ');
 | 
				
			||||||
 | 
							return trimmed.substring(trimmed.charAt(0) == '/' ? 1 : 0, index < 1 ? trimmed.length() : index);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Examines whether a key can be put in the map and logs it with
 | 
				
			||||||
 | 
						 * {@code Level.WARNING} if that key violates API constrictions.<br>
 | 
				
			||||||
 | 
						 * (allowed chars are <b>a-zA-Z0-9_:!()?.,;-</b>)
 | 
				
			||||||
 | 
						 * <p>
 | 
				
			||||||
 | 
						 * The approach to not throw an exception was taken so that an ugly try-catch
 | 
				
			||||||
 | 
						 * block for every addition to the system commands map could be avoided, an
 | 
				
			||||||
 | 
						 * error that should only occur during implementation and not in production.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param command the key to examine
 | 
				
			||||||
 | 
						 * @return whether this key can be used in the map
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public boolean isValidKey(String command) {
 | 
				
			||||||
 | 
							final boolean valid = commandPattern.matcher(command).matches();
 | 
				
			||||||
 | 
							if (!valid) logger.log(Level.WARNING,
 | 
				
			||||||
 | 
									"The command \"" + command
 | 
				
			||||||
 | 
											+ "\" is not valid. As it will cause problems in execution, it will not be entered into the map. Only the characters "
 | 
				
			||||||
 | 
											+ commandPattern + "are allowed");
 | 
				
			||||||
 | 
							return valid;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Takes a 'raw' string (the whole input) and checks if "/" is the first visible
 | 
				
			||||||
 | 
						 * character and then checks if a command is present after that "/". If that is
 | 
				
			||||||
 | 
						 * the case, it will be executed.
 | 
				
			||||||
 | 
						 * <p>
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param raw the raw input string
 | 
				
			||||||
 | 
						 * @return whether a command could be found
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public boolean executeIfAnyPresent(String raw) {
 | 
				
			||||||
 | 
							// possibly a command was detected and could be executed
 | 
				
			||||||
 | 
							final var	raw2			= raw.stripLeading();
 | 
				
			||||||
 | 
							final var	commandFound	= raw2.startsWith("/") ? executeIfPresent(raw2) : false;
 | 
				
			||||||
 | 
							// the command was executed successfully - no further checking needed
 | 
				
			||||||
 | 
							if (commandFound) logger.log(Level.FINE, "executed system command " + getCommand(raw2));
 | 
				
			||||||
 | 
							return commandFound;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * This method checks if the input String is a key in the map and executes the
 | 
				
			||||||
 | 
						 * wrapped System command if present.
 | 
				
			||||||
 | 
						 * Its intended usage is after a "/" has been detected in the input String.
 | 
				
			||||||
 | 
						 * It will do nothing if the value after the slash is not a key in
 | 
				
			||||||
 | 
						 * the map, which is a valid case (i.e. input="3/4" and "4" is not a key in the
 | 
				
			||||||
 | 
						 * map).
 | 
				
			||||||
 | 
						 * <p>
 | 
				
			||||||
 | 
						 * Usage example:<br>
 | 
				
			||||||
 | 
						 * {@code SystemCommandsMap systemCommands = new SystemCommandsMap();}<br>
 | 
				
			||||||
 | 
						 * {@code Button button = new Button();}<br>
 | 
				
			||||||
 | 
						 * {@code systemCommands.add("example", (words)-> button.setText(words.get(0), 1);}<br>
 | 
				
			||||||
 | 
						 * {@code ....}<br>
 | 
				
			||||||
 | 
						 * user input: {@code "/example xyz ..."}<br>
 | 
				
			||||||
 | 
						 * {@code systemCommands.executeIfPresent("example xyz ...")}
 | 
				
			||||||
 | 
						 * result: {@code button.getText()=="xyz"}
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param input the input string given by the user
 | 
				
			||||||
 | 
						 * @return whether a command could be found
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public boolean executeIfPresent(String input) {
 | 
				
			||||||
 | 
							final var	command	= getCommand(input);
 | 
				
			||||||
 | 
							final var	value	= get(command);
 | 
				
			||||||
 | 
							value.ifPresent(systemCommand -> {
 | 
				
			||||||
 | 
								// Splitting the String so that the leading command including the first " " is
 | 
				
			||||||
 | 
								// removed and only as many following words as allowed by the system command
 | 
				
			||||||
 | 
								// persist
 | 
				
			||||||
 | 
								final var arguments = extractArguments(input, systemCommand);
 | 
				
			||||||
 | 
								// Executing the function
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									systemCommand.getAction().accept(arguments);
 | 
				
			||||||
 | 
									systemCommand.onCall();
 | 
				
			||||||
 | 
								} catch (final Exception e) {
 | 
				
			||||||
 | 
									logger.log(Level.WARNING, "The system command " + command + " threw an exception: ", e);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							return value.isPresent();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Supplies missing values with default values.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param input         the input String
 | 
				
			||||||
 | 
						 * @param systemCommand the command that is expected
 | 
				
			||||||
 | 
						 * @return the list of arguments that can be used to parse the systemCommand
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private List<String> extractArguments(String input, SystemCommand systemCommand) {
 | 
				
			||||||
 | 
							// no more arguments follow after the command (e.g. text = "/DABR")
 | 
				
			||||||
 | 
							final var indexOfSpace = input.indexOf(" ");
 | 
				
			||||||
 | 
							if (indexOfSpace < 0) return supplementDefaults(new String[] {}, systemCommand);
 | 
				
			||||||
 | 
							// the arguments behind a system command
 | 
				
			||||||
 | 
							final var	remainingString		= input.substring(indexOfSpace + 1);
 | 
				
			||||||
 | 
							final var	numberOfArguments	= systemCommand.getNumberOfArguments();
 | 
				
			||||||
 | 
							// splitting those arguments and supplying default values
 | 
				
			||||||
 | 
							final var	textArguments		= remainingString.split(" ", -1);
 | 
				
			||||||
 | 
							final var	originalArguments	= numberOfArguments >= 0 ? Arrays.copyOfRange(textArguments, 0, numberOfArguments) : textArguments;
 | 
				
			||||||
 | 
							final var	arguments			= supplementDefaults(originalArguments, systemCommand);
 | 
				
			||||||
 | 
							return arguments;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Retrieves the recommendations based on the current input entered.<br>
 | 
				
			||||||
 | 
						 * The first word is used for the recommendations and
 | 
				
			||||||
 | 
						 * it does not matter if the "/" is at its beginning or not.<br>
 | 
				
			||||||
 | 
						 * If none are present, nothing will be done.<br>
 | 
				
			||||||
 | 
						 * Otherwise the given function will be executed on the recommendations.<br>
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param input  the input string
 | 
				
			||||||
 | 
						 * @param action the action that should be taken for the recommendations, if any
 | 
				
			||||||
 | 
						 *               are present
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void requestRecommendations(String input, Function<Set<String>, Void> action) {
 | 
				
			||||||
 | 
							final var partialCommand = getCommand(input);
 | 
				
			||||||
 | 
							// Get the expected commands
 | 
				
			||||||
 | 
							final var recommendations = recommendCommands(partialCommand);
 | 
				
			||||||
 | 
							if (recommendations.isEmpty()) return;
 | 
				
			||||||
 | 
							// Execute the given action
 | 
				
			||||||
 | 
							else action.apply(recommendations);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Recommends commands based upon the currently entered input.<br>
 | 
				
			||||||
 | 
						 * In the current implementation, all we check is whether a key contains this
 | 
				
			||||||
 | 
						 * input. This might be updated later on.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param partialCommand the partially entered command
 | 
				
			||||||
 | 
						 * @return a set of all commands that match this input
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private Set<String> recommendCommands(String partialCommand) {
 | 
				
			||||||
 | 
							// current implementation only looks if input is contained within a command,
 | 
				
			||||||
 | 
							// might be updated
 | 
				
			||||||
 | 
							return systemCommands.keySet()
 | 
				
			||||||
 | 
								.stream()
 | 
				
			||||||
 | 
								.filter(command -> command.contains(partialCommand))
 | 
				
			||||||
 | 
								.sorted((command1, command2) -> Integer.compare(systemCommands.get(command1).getRelevance(), systemCommands.get(command2).getRelevance()))
 | 
				
			||||||
 | 
								.collect(Collectors.toSet());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * Supplies the default values for arguments if none are present in the text for
 | 
				
			||||||
 | 
						 * any argument. <br>
 | 
				
			||||||
 | 
						 * Will only work for {@code SystemCommand}s whose argument counter is bigger
 | 
				
			||||||
 | 
						 * than 1.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param textArguments the arguments that were parsed from the text
 | 
				
			||||||
 | 
						 * @param toEvaluate    the system command whose default values should be used
 | 
				
			||||||
 | 
						 * @return the final argument list
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 * @apiNote this method will insert an empty String if the size of the list
 | 
				
			||||||
 | 
						 *          given to the {@code SystemCommand} is smaller than its argument
 | 
				
			||||||
 | 
						 *          counter and no more text arguments could be found.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private List<String> supplementDefaults(String[] textArguments, SystemCommand toEvaluate) {
 | 
				
			||||||
 | 
							final var			defaults			= toEvaluate.getDefaults();
 | 
				
			||||||
 | 
							final var			numberOfArguments	= toEvaluate.getNumberOfArguments();
 | 
				
			||||||
 | 
							final List<String>	result				= new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (toEvaluate.getNumberOfArguments() > 0) for (int index = 0; index < numberOfArguments; index++) {
 | 
				
			||||||
 | 
								String textArg = null;
 | 
				
			||||||
 | 
								if (index < textArguments.length) textArg = textArguments[index];
 | 
				
			||||||
 | 
								// Set the argument at position index to the current argument of the text, if it
 | 
				
			||||||
 | 
								// is present. Otherwise the default for that argument will be taken if present.
 | 
				
			||||||
 | 
								// In the worst case, an empty String will be used.
 | 
				
			||||||
 | 
								result.add(!(textArg == null) && !textArg.isBlank() ? textArg : index < defaults.size() ? defaults.get(index) : "");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return all {@link SystemCommand}s used with the underlying command as key
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Map<String, SystemCommand> getSystemCommands() { return systemCommands; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This package contains all classes that can be used as system commands.<br>
 | 
				
			||||||
 | 
					 * Every system command can be called using a specific syntax:"/<command>"
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>package-info.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>16.07.2020</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package envoy.client.data.commands;
 | 
				
			||||||
@@ -155,6 +155,9 @@ public class Client implements Closeable {
 | 
				
			|||||||
		// Process group size changes
 | 
							// Process group size changes
 | 
				
			||||||
		receiver.registerProcessor(GroupResize.class, evt -> { localDB.updateGroup(evt); eventBus.dispatch(evt); });
 | 
							receiver.registerProcessor(GroupResize.class, evt -> { localDB.updateGroup(evt); eventBus.dispatch(evt); });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Process IsTyping events
 | 
				
			||||||
 | 
							receiver.registerProcessor(IsTyping.class, eventBus::dispatch);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Send event
 | 
							// Send event
 | 
				
			||||||
		eventBus.register(SendEvent.class, evt -> {
 | 
							eventBus.register(SendEvent.class, evt -> {
 | 
				
			||||||
			try {
 | 
								try {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,6 @@
 | 
				
			|||||||
package envoy.client.net;
 | 
					package envoy.client.net;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.ByteArrayInputStream;
 | 
					import java.io.*;
 | 
				
			||||||
import java.io.InputStream;
 | 
					 | 
				
			||||||
import java.io.ObjectInputStream;
 | 
					 | 
				
			||||||
import java.net.SocketException;
 | 
					import java.net.SocketException;
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
@@ -82,8 +80,9 @@ public class Receiver extends Thread {
 | 
				
			|||||||
						logger.log(Level.WARNING, String.format("The received object has the %s for which no processor is defined.", obj.getClass()));
 | 
											logger.log(Level.WARNING, String.format("The received object has the %s for which no processor is defined.", obj.getClass()));
 | 
				
			||||||
					else processor.accept(obj);
 | 
										else processor.accept(obj);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} catch (final SocketException e) {
 | 
								} catch (final SocketException | EOFException e) {
 | 
				
			||||||
				// Connection probably closed by client.
 | 
									// Connection probably closed by client.
 | 
				
			||||||
 | 
									logger.log(Level.FINER, "Exiting receiver...");
 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			} 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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,13 @@
 | 
				
			|||||||
package envoy.client.ui;
 | 
					package envoy.client.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.image.BufferedImage;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.util.EnumMap;
 | 
					import java.util.EnumMap;
 | 
				
			||||||
import java.util.EnumSet;
 | 
					import java.util.EnumSet;
 | 
				
			||||||
import java.util.logging.Level;
 | 
					import java.util.logging.Level;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.imageio.ImageIO;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javafx.scene.image.Image;
 | 
					import javafx.scene.image.Image;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Settings;
 | 
					import envoy.client.data.Settings;
 | 
				
			||||||
@@ -145,6 +149,23 @@ public class IconUtil {
 | 
				
			|||||||
		return icons;
 | 
							return icons;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Loads a buffered image from the resource folder which is compatible with AWT.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param path the path to the icon inside the resource folder
 | 
				
			||||||
 | 
						 * @return the loaded image
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static BufferedImage loadAWTCompatible(String path) {
 | 
				
			||||||
 | 
							BufferedImage image = null;
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								image = ImageIO.read(IconUtil.class.getResource(path));
 | 
				
			||||||
 | 
							} catch (IOException e) {
 | 
				
			||||||
 | 
								EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING, String.format("Could not load image at path %s: ", path), e);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return image;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * This method should be called if the display of an image depends upon the
 | 
						 * This method should be called if the display of an image depends upon the
 | 
				
			||||||
	 * currently active theme.<br>
 | 
						 * currently active theme.<br>
 | 
				
			||||||
@@ -154,7 +175,7 @@ public class IconUtil {
 | 
				
			|||||||
	 * @return the theme specific folder
 | 
						 * @return the theme specific folder
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static String themeSpecificSubFolder() {
 | 
						private static String themeSpecificSubFolder() {
 | 
				
			||||||
		return Settings.getInstance().isUsingDefaultTheme() ? Settings.getInstance().getCurrentTheme() + "/" : "";
 | 
							return Settings.getInstance().isUsingDefaultTheme() ? Settings.getInstance().getCurrentTheme() + "/" : "";
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,15 +2,13 @@ package envoy.client.ui;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.awt.*;
 | 
					import java.awt.*;
 | 
				
			||||||
import java.awt.TrayIcon.MessageType;
 | 
					import java.awt.TrayIcon.MessageType;
 | 
				
			||||||
import java.awt.event.WindowAdapter;
 | 
					
 | 
				
			||||||
import java.awt.event.WindowEvent;
 | 
					import javafx.application.Platform;
 | 
				
			||||||
import java.util.logging.Level;
 | 
					import javafx.stage.Stage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.event.MessageCreationEvent;
 | 
					import envoy.client.event.MessageCreationEvent;
 | 
				
			||||||
import envoy.data.Message;
 | 
					import envoy.data.Message;
 | 
				
			||||||
import envoy.event.EventBus;
 | 
					import envoy.event.EventBus;
 | 
				
			||||||
import envoy.exception.EnvoyException;
 | 
					 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>envoy-client</strong><br>
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
@@ -35,66 +33,65 @@ public class StatusTrayIcon {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	private boolean displayMessages = false;
 | 
						private boolean displayMessages = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return {@code true} if the status tray icon is supported on this platform
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static boolean isSupported() { return SystemTray.isSupported(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up
 | 
						 * Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up
 | 
				
			||||||
	 * menu.
 | 
						 * menu.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param focusTarget the {@link Window} which focus determines if message
 | 
						 * @param stage the stage whose focus determines if message
 | 
				
			||||||
	 *              notifications are displayed
 | 
						 *              notifications are displayed
 | 
				
			||||||
	 * @throws EnvoyException if the currently used OS does not support the System
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 *                        Tray API
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
					 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public StatusTrayIcon(Window focusTarget) throws EnvoyException {
 | 
						public StatusTrayIcon(Stage stage) {
 | 
				
			||||||
		if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported.");
 | 
							trayIcon = new TrayIcon(IconUtil.loadAWTCompatible("/icons/envoy_logo.png"), "Envoy");
 | 
				
			||||||
 | 
					 | 
				
			||||||
		final ClassLoader	loader	= Thread.currentThread().getContextClassLoader();
 | 
					 | 
				
			||||||
		final Image			img		= Toolkit.getDefaultToolkit().createImage(loader.getResource("envoy_logo.png"));
 | 
					 | 
				
			||||||
		trayIcon = new TrayIcon(img, "Envoy Client");
 | 
					 | 
				
			||||||
		trayIcon.setImageAutoSize(true);
 | 
							trayIcon.setImageAutoSize(true);
 | 
				
			||||||
		trayIcon.setToolTip("You are notified if you have unread messages.");
 | 
							trayIcon.setToolTip("You are notified if you have unread messages.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final PopupMenu popup = new PopupMenu();
 | 
							final PopupMenu popup = new PopupMenu();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final MenuItem exitMenuItem = new MenuItem("Exit");
 | 
							final MenuItem exitMenuItem = new MenuItem("Exit");
 | 
				
			||||||
		exitMenuItem.addActionListener(evt -> System.exit(0));
 | 
							exitMenuItem.addActionListener(evt -> { Platform.exit(); System.exit(0); });
 | 
				
			||||||
		popup.add(exitMenuItem);
 | 
							popup.add(exitMenuItem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		trayIcon.setPopupMenu(popup);
 | 
							trayIcon.setPopupMenu(popup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Only display messages if the chat window is not focused
 | 
							// Only display messages if the stage is not focused
 | 
				
			||||||
		focusTarget.addWindowFocusListener(new WindowAdapter() {
 | 
							stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = !ov.getValue());
 | 
				
			||||||
 | 
					 | 
				
			||||||
			@Override
 | 
					 | 
				
			||||||
			public void windowGainedFocus(WindowEvent e) { displayMessages = false; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			@Override
 | 
					 | 
				
			||||||
			public void windowLostFocus(WindowEvent e) { displayMessages = true; }
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Show the window if the user clicks on the icon
 | 
							// Show the window if the user clicks on the icon
 | 
				
			||||||
		trayIcon.addActionListener(evt -> { focusTarget.setVisible(true); focusTarget.requestFocus(); });
 | 
							trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.setIconified(false); stage.toFront(); stage.requestFocus(); }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Start processing message events
 | 
							// Start processing message events
 | 
				
			||||||
		// TODO: Handle other message types
 | 
							EventBus.getInstance().register(MessageCreationEvent.class, evt -> {
 | 
				
			||||||
		EventBus.getInstance()
 | 
								if (displayMessages) trayIcon
 | 
				
			||||||
			.register(MessageCreationEvent.class,
 | 
									.displayMessage(
 | 
				
			||||||
					evt -> { if (displayMessages) trayIcon.displayMessage("New message received", evt.get().getText(), MessageType.INFO); });
 | 
											evt.get().hasAttachment() ? "New " + evt.get().getAttachment().getType().toString().toLowerCase() + " message received"
 | 
				
			||||||
 | 
													: "New message received",
 | 
				
			||||||
 | 
											evt.get().getText(),
 | 
				
			||||||
 | 
											MessageType.INFO);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Makes this {@link StatusTrayIcon} appear in the system tray.
 | 
						 * Makes the icon appear in the system tray.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @throws EnvoyException if the status icon could not be attaches to the system
 | 
					 | 
				
			||||||
	 *                        tray for system-internal reasons
 | 
					 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
						 * @since Envoy Client v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void show() throws EnvoyException {
 | 
						public void show() {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			SystemTray.getSystemTray().add(trayIcon);
 | 
								SystemTray.getSystemTray().add(trayIcon);
 | 
				
			||||||
		} catch (final AWTException e) {
 | 
							} catch (AWTException e) {}
 | 
				
			||||||
			EnvoyLog.getLogger(StatusTrayIcon.class).log(Level.INFO, "Could not display StatusTrayIcon: ", e);
 | 
					 | 
				
			||||||
			throw new EnvoyException("Could not attach Envoy tray icon to system tray.", e);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Removes the icon from the system tray.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void hide() { SystemTray.getSystemTray().remove(trayIcon); }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,8 @@ import java.io.ByteArrayInputStream;
 | 
				
			|||||||
import java.io.File;
 | 
					import java.io.File;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.nio.file.Files;
 | 
					import java.nio.file.Files;
 | 
				
			||||||
 | 
					import java.time.LocalDateTime;
 | 
				
			||||||
 | 
					import java.time.format.DateTimeFormatter;
 | 
				
			||||||
import java.util.Random;
 | 
					import java.util.Random;
 | 
				
			||||||
import java.util.logging.Level;
 | 
					import java.util.logging.Level;
 | 
				
			||||||
import java.util.logging.Logger;
 | 
					import java.util.logging.Logger;
 | 
				
			||||||
@@ -33,7 +35,10 @@ import javafx.util.Duration;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.*;
 | 
					import envoy.client.data.*;
 | 
				
			||||||
import envoy.client.data.audio.AudioRecorder;
 | 
					import envoy.client.data.audio.AudioRecorder;
 | 
				
			||||||
 | 
					import envoy.client.data.commands.SystemCommandBuilder;
 | 
				
			||||||
 | 
					import envoy.client.data.commands.SystemCommandsMap;
 | 
				
			||||||
import envoy.client.event.MessageCreationEvent;
 | 
					import envoy.client.event.MessageCreationEvent;
 | 
				
			||||||
 | 
					import envoy.client.event.SendEvent;
 | 
				
			||||||
import envoy.client.net.Client;
 | 
					import envoy.client.net.Client;
 | 
				
			||||||
import envoy.client.net.WriteProxy;
 | 
					import envoy.client.net.WriteProxy;
 | 
				
			||||||
import envoy.client.ui.*;
 | 
					import envoy.client.ui.*;
 | 
				
			||||||
@@ -126,6 +131,8 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
	private Attachment		pendingAttachment;
 | 
						private Attachment		pendingAttachment;
 | 
				
			||||||
	private boolean			postingPermanentlyDisabled;
 | 
						private boolean			postingPermanentlyDisabled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final SystemCommandsMap messageTextAreaCommands = new SystemCommandsMap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final Settings	settings	= Settings.getInstance();
 | 
						private static final Settings	settings	= Settings.getInstance();
 | 
				
			||||||
	private static final EventBus	eventBus	= EventBus.getInstance();
 | 
						private static final EventBus	eventBus	= EventBus.getInstance();
 | 
				
			||||||
	private static final Logger		logger		= EnvoyLog.getLogger(ChatScene.class);
 | 
						private static final Logger		logger		= EnvoyLog.getLogger(ChatScene.class);
 | 
				
			||||||
@@ -221,7 +228,7 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
			switch (e.getOperationType()) {
 | 
								switch (e.getOperationType()) {
 | 
				
			||||||
				case ADD:
 | 
									case ADD:
 | 
				
			||||||
					if (contact instanceof User) localDB.getUsers().put(contact.getName(), (User) contact);
 | 
										if (contact instanceof User) localDB.getUsers().put(contact.getName(), (User) contact);
 | 
				
			||||||
					Chat chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
 | 
										final Chat chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
 | 
				
			||||||
					Platform.runLater(() -> chatList.getItems().add(chat));
 | 
										Platform.runLater(() -> chatList.getItems().add(chat));
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				case REMOVE:
 | 
									case REMOVE:
 | 
				
			||||||
@@ -231,6 +238,22 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Initializes all {@code SystemCommands} used in {@code ChatScene}.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private void initializeSystemCommandsMap() {
 | 
				
			||||||
 | 
							final var builder = new SystemCommandBuilder();
 | 
				
			||||||
 | 
							// Do A Barrel roll initialization
 | 
				
			||||||
 | 
							final var random = new Random();
 | 
				
			||||||
 | 
							builder.setAction(text -> doABarrelRoll(Integer.parseInt(text.get(0)), Double.parseDouble(text.get(1))))
 | 
				
			||||||
 | 
								.setDefaults(Integer.toString(random.nextInt(3) + 1), Double.toString(random.nextDouble() * 3 + 1))
 | 
				
			||||||
 | 
								.setDescription("See for yourself :)")
 | 
				
			||||||
 | 
								.setNumberOfArguments(2);
 | 
				
			||||||
 | 
							messageTextAreaCommands.add("DABR", builder.build());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Initializes all necessary data via dependency injection-
 | 
						 * Initializes all necessary data via dependency injection-
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
@@ -251,9 +274,12 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
		chatList.setItems(chats);
 | 
							chatList.setItems(chats);
 | 
				
			||||||
		contactLabel.setText(localDB.getUser().getName());
 | 
							contactLabel.setText(localDB.getUser().getName());
 | 
				
			||||||
		MessageControl.setLocalDB(localDB);
 | 
							MessageControl.setLocalDB(localDB);
 | 
				
			||||||
 | 
					    MessageControl.setSceneContext(sceneContext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info");
 | 
							if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		recorder = new AudioRecorder();
 | 
							recorder = new AudioRecorder();
 | 
				
			||||||
 | 
							initializeSystemCommandsMap();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
@@ -362,7 +388,9 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
					});
 | 
										});
 | 
				
			||||||
					recorder.start();
 | 
										recorder.start();
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
					pendingAttachment	= new Attachment(recorder.finish(), AttachmentType.VOICE);
 | 
										pendingAttachment	= new Attachment(recorder.finish(), "Voice_recording_"
 | 
				
			||||||
 | 
												+ DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss").format(LocalDateTime.now()) + "." + AudioRecorder.FILE_FORMAT,
 | 
				
			||||||
 | 
												AttachmentType.VOICE);
 | 
				
			||||||
					recording			= false;
 | 
										recording			= false;
 | 
				
			||||||
					Platform.runLater(() -> {
 | 
										Platform.runLater(() -> {
 | 
				
			||||||
						voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
											voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
@@ -413,7 +441,7 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
			// Create the pending attachment
 | 
								// Create the pending attachment
 | 
				
			||||||
			try {
 | 
								try {
 | 
				
			||||||
				final var fileBytes = Files.readAllBytes(file.toPath());
 | 
									final var fileBytes = Files.readAllBytes(file.toPath());
 | 
				
			||||||
				pendingAttachment = new Attachment(fileBytes, type);
 | 
									pendingAttachment = new Attachment(fileBytes, file.getName(), type);
 | 
				
			||||||
				checkPostConditions(false);
 | 
									checkPostConditions(false);
 | 
				
			||||||
				// Setting the preview image as image of the attachmentView
 | 
									// Setting the preview image as image of the attachmentView
 | 
				
			||||||
				if (type == AttachmentType.PICTURE)
 | 
									if (type == AttachmentType.PICTURE)
 | 
				
			||||||
@@ -426,20 +454,34 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Rotates every element in our application by 360° in at most 2.75s.
 | 
						 * Rotates every element in our application by (at most 4 *) 360° in at most
 | 
				
			||||||
 | 
						 * 2.75s.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void doABarrelRoll() {
 | 
						private void doABarrelRoll() {
 | 
				
			||||||
		// contains all Node objects in ChatScene in alphabetical order
 | 
					 | 
				
			||||||
		final var	rotatableNodes	= new Node[] { attachmentButton, attachmentView, contactLabel, infoLabel, messageList, messageTextArea,
 | 
					 | 
				
			||||||
				postButton, remainingChars, rotateButton, scene, settingsButton, chatList, voiceButton };
 | 
					 | 
				
			||||||
		final var random = new Random();
 | 
							final var random = new Random();
 | 
				
			||||||
 | 
							doABarrelRoll(random.nextInt(3) + 1, random.nextDouble() * 3 + 1);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Rotates every element in our application by {@code rotations}*360° in
 | 
				
			||||||
 | 
						 * {@code an}.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param rotations     the amount of times the scene is rotated by 360°
 | 
				
			||||||
 | 
						 * @param animationTime the time in seconds that this animation lasts
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private void doABarrelRoll(int rotations, double animationTime) {
 | 
				
			||||||
 | 
							// contains all Node objects in ChatScene in alphabetical order
 | 
				
			||||||
 | 
							final var rotatableNodes = new Node[] { attachmentButton, attachmentView, contactLabel, infoLabel, messageList, messageTextArea, postButton,
 | 
				
			||||||
 | 
									remainingChars, rotateButton, scene, settingsButton, chatList, voiceButton };
 | 
				
			||||||
		for (final var node : rotatableNodes) {
 | 
							for (final var node : rotatableNodes) {
 | 
				
			||||||
			// Defines at most four whole rotation in at most 4s
 | 
								// Sets the animation duration to {animationTime}
 | 
				
			||||||
			final var rotateTransition = new RotateTransition(Duration.seconds(random.nextDouble() * 3 + 1), node);
 | 
								final var rotateTransition = new RotateTransition(Duration.seconds(animationTime), node);
 | 
				
			||||||
			rotateTransition.setByAngle((random.nextInt(3) + 1) * 360);
 | 
								// rotates every element {rotations} times
 | 
				
			||||||
 | 
								rotateTransition.setByAngle(rotations * 360);
 | 
				
			||||||
			rotateTransition.play();
 | 
								rotateTransition.play();
 | 
				
			||||||
			// This is needed as for some strange reason objects could stop before being
 | 
								// This is needed as for some strange reason objects could stop before being
 | 
				
			||||||
			// rotated back to 0°
 | 
								// rotated back to 0°
 | 
				
			||||||
@@ -459,10 +501,28 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
	private void checkKeyCombination(KeyEvent e) {
 | 
						private void checkKeyCombination(KeyEvent e) {
 | 
				
			||||||
		// Checks whether the text is too long
 | 
							// Checks whether the text is too long
 | 
				
			||||||
		messageTextUpdated();
 | 
							messageTextUpdated();
 | 
				
			||||||
 | 
							// Sending an IsTyping event if none has been sent for
 | 
				
			||||||
 | 
							// IsTyping#millisecondsActive
 | 
				
			||||||
 | 
							if (client.isOnline() && currentChat.getLastWritingEvent() + IsTyping.millisecondsActive <= System.currentTimeMillis()) {
 | 
				
			||||||
 | 
								eventBus.dispatch(new SendEvent(new IsTyping(getChatID(), currentChat.getRecipient().getID())));
 | 
				
			||||||
 | 
								currentChat.lastWritingEventWasNow();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		// Automatic sending of messages via (ctrl +) enter
 | 
							// Automatic sending of messages via (ctrl +) enter
 | 
				
			||||||
		checkPostConditions(e);
 | 
							checkPostConditions(e);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Returns the id that should be used to send things to the server:
 | 
				
			||||||
 | 
						 * the id of 'our' {@link User} if the recipient of that object is another User,
 | 
				
			||||||
 | 
						 * else the id of the {@link Group} 'our' user is sending to.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @return an id that can be sent to the server
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private long getChatID() {
 | 
				
			||||||
 | 
							return currentChat.getRecipient() instanceof User ? client.getSender().getID() : currentChat.getRecipient().getID();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param e the keys that have been pressed
 | 
						 * @param e the keys that have been pressed
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
@@ -531,7 +591,7 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		final var text = messageTextArea.getText().strip();
 | 
							final var text = messageTextArea.getText().strip();
 | 
				
			||||||
		try {
 | 
							if (!messageTextAreaCommands.executeIfAnyPresent(text)) try {
 | 
				
			||||||
			// Creating the message and its metadata
 | 
								// Creating the message and its metadata
 | 
				
			||||||
			final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
 | 
								final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
 | 
				
			||||||
				.setText(text);
 | 
									.setText(text);
 | 
				
			||||||
@@ -619,10 +679,15 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
	private void copyAndPostMessage() {
 | 
						private void copyAndPostMessage() {
 | 
				
			||||||
		final var messageText = messageTextArea.getText();
 | 
							final var messageText = messageTextArea.getText();
 | 
				
			||||||
		Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(messageText), null);
 | 
							Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(messageText), null);
 | 
				
			||||||
 | 
							final var	image				= attachmentView.getImage();
 | 
				
			||||||
 | 
							final var	messageAttachment	= pendingAttachment;
 | 
				
			||||||
		postMessage();
 | 
							postMessage();
 | 
				
			||||||
		messageTextArea.setText(messageText);
 | 
							messageTextArea.setText(messageText);
 | 
				
			||||||
		updateRemainingCharsLabel();
 | 
							updateRemainingCharsLabel();
 | 
				
			||||||
		postButton.setDisable(messageText.isBlank());
 | 
							postButton.setDisable(messageText.isBlank());
 | 
				
			||||||
 | 
							attachmentView.setImage(image);
 | 
				
			||||||
 | 
							if (attachmentView.getImage() != null) attachmentView.setVisible(true);
 | 
				
			||||||
 | 
							pendingAttachment = messageAttachment;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
package envoy.client.ui.controller;
 | 
					package envoy.client.ui.controller;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
import java.util.logging.Level;
 | 
					import java.util.logging.Level;
 | 
				
			||||||
import java.util.logging.Logger;
 | 
					import java.util.logging.Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,7 +54,19 @@ public class ContactSearchScene {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private LocalDB localDB;
 | 
						private LocalDB localDB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static EventBus		eventBus	= EventBus.getInstance();
 | 
						private Alert alert = new Alert(AlertType.CONFIRMATION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private User currentlySelectedUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Consumer<ContactOperation> handler = e -> {
 | 
				
			||||||
 | 
							final var contact = e.get();
 | 
				
			||||||
 | 
							if (e.getOperationType() == ElementOperation.ADD) Platform.runLater(() -> {
 | 
				
			||||||
 | 
								userList.getItems().remove(contact);
 | 
				
			||||||
 | 
								if (currentlySelectedUser != null && currentlySelectedUser.equals(contact) && alert.isShowing()) alert.close();
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final EventBus	eventBus	= EventBus.getInstance();
 | 
				
			||||||
	private static final Logger		logger		= EnvoyLog.getLogger(ChatScene.class);
 | 
						private static final Logger		logger		= EnvoyLog.getLogger(ChatScene.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -72,6 +85,7 @@ public class ContactSearchScene {
 | 
				
			|||||||
		searchBar.setClearButtonListener(e -> { searchBar.getTextField().clear(); userList.getItems().clear(); });
 | 
							searchBar.setClearButtonListener(e -> { searchBar.getTextField().clear(); userList.getItems().clear(); });
 | 
				
			||||||
		eventBus.register(UserSearchResult.class,
 | 
							eventBus.register(UserSearchResult.class,
 | 
				
			||||||
				response -> Platform.runLater(() -> { userList.getItems().clear(); userList.getItems().addAll(response.get()); }));
 | 
									response -> Platform.runLater(() -> { userList.getItems().clear(); userList.getItems().addAll(response.get()); }));
 | 
				
			||||||
 | 
							eventBus.register(ContactOperation.class, handler);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -108,20 +122,21 @@ public class ContactSearchScene {
 | 
				
			|||||||
	private void userListClicked() {
 | 
						private void userListClicked() {
 | 
				
			||||||
		final var user = userList.getSelectionModel().getSelectedItem();
 | 
							final var user = userList.getSelectionModel().getSelectedItem();
 | 
				
			||||||
		if (user != null) {
 | 
							if (user != null) {
 | 
				
			||||||
			final var alert = new Alert(AlertType.CONFIRMATION);
 | 
								currentlySelectedUser	= user;
 | 
				
			||||||
			alert.setTitle("Add Contact to Contact List");
 | 
								alert					= new Alert(AlertType.CONFIRMATION);
 | 
				
			||||||
			alert.setHeaderText("Add the user " + user.getName() + " to your contact list?");
 | 
								alert.setTitle("Add User to Contact List");
 | 
				
			||||||
 | 
								alert.setHeaderText("Add the user " + currentlySelectedUser.getName() + " to your contact list?");
 | 
				
			||||||
			// Normally, this would be total BS (we are already on the FX Thread), however
 | 
								// Normally, this would be total BS (we are already on the FX Thread), however
 | 
				
			||||||
			// it could be proven that the creation of this dialog wrapped in
 | 
								// it could be proven that the creation of this dialog wrapped in
 | 
				
			||||||
			// Platform.runLater is less error-prone than without it
 | 
								// Platform.runLater is less error-prone than without it
 | 
				
			||||||
			Platform.runLater(() -> alert.showAndWait().filter(btn -> btn == ButtonType.OK).ifPresent(btn -> {
 | 
								Platform.runLater(() -> alert.showAndWait().filter(btn -> btn == ButtonType.OK).ifPresent(btn -> {
 | 
				
			||||||
				final var event = new ContactOperation(user, ElementOperation.ADD);
 | 
									final var event = new ContactOperation(currentlySelectedUser, ElementOperation.ADD);
 | 
				
			||||||
				// Sends the event to the server
 | 
									// Sends the event to the server
 | 
				
			||||||
				eventBus.dispatch(new SendEvent(event));
 | 
									eventBus.dispatch(new SendEvent(event));
 | 
				
			||||||
				// Removes the chosen user and updates the UI
 | 
									// Removes the chosen user and updates the UI
 | 
				
			||||||
				userList.getItems().remove(user);
 | 
									userList.getItems().remove(currentlySelectedUser);
 | 
				
			||||||
				eventBus.dispatch(event);
 | 
									eventBus.dispatch(event);
 | 
				
			||||||
				logger.log(Level.INFO, "Added user " + user);
 | 
									logger.log(Level.INFO, "Added user " + currentlySelectedUser);
 | 
				
			||||||
			}));
 | 
								}));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,9 +17,7 @@ import javafx.scene.image.ImageView;
 | 
				
			|||||||
import envoy.client.data.*;
 | 
					import envoy.client.data.*;
 | 
				
			||||||
import envoy.client.net.Client;
 | 
					import envoy.client.net.Client;
 | 
				
			||||||
import envoy.client.net.WriteProxy;
 | 
					import envoy.client.net.WriteProxy;
 | 
				
			||||||
import envoy.client.ui.IconUtil;
 | 
					import envoy.client.ui.*;
 | 
				
			||||||
import envoy.client.ui.SceneContext;
 | 
					 | 
				
			||||||
import envoy.client.ui.Startup;
 | 
					 | 
				
			||||||
import envoy.data.LoginCredentials;
 | 
					import envoy.data.LoginCredentials;
 | 
				
			||||||
import envoy.data.User;
 | 
					import envoy.data.User;
 | 
				
			||||||
import envoy.data.User.UserStatus;
 | 
					import envoy.data.User.UserStatus;
 | 
				
			||||||
@@ -77,6 +75,7 @@ public final class LoginScene {
 | 
				
			|||||||
	private static final Logger			logger		= EnvoyLog.getLogger(LoginScene.class);
 | 
						private static final Logger			logger		= EnvoyLog.getLogger(LoginScene.class);
 | 
				
			||||||
	private static final EventBus		eventBus	= EventBus.getInstance();
 | 
						private static final EventBus		eventBus	= EventBus.getInstance();
 | 
				
			||||||
	private static final ClientConfig	config		= ClientConfig.getInstance();
 | 
						private static final ClientConfig	config		= ClientConfig.getInstance();
 | 
				
			||||||
 | 
						private static final Settings		settings	= Settings.getInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void initialize() {
 | 
						private void initialize() {
 | 
				
			||||||
@@ -239,5 +238,23 @@ public final class LoginScene {
 | 
				
			|||||||
		sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE);
 | 
							sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE);
 | 
				
			||||||
		sceneContext.<ChatScene>getController().initializeData(sceneContext, localDB, client, writeProxy);
 | 
							sceneContext.<ChatScene>getController().initializeData(sceneContext, localDB, client, writeProxy);
 | 
				
			||||||
		sceneContext.getStage().centerOnScreen();
 | 
							sceneContext.getStage().centerOnScreen();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (StatusTrayIcon.isSupported()) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Configure hide on close
 | 
				
			||||||
 | 
								sceneContext.getStage().setOnCloseRequest(e -> {
 | 
				
			||||||
 | 
									if (settings.isHideOnClose()) {
 | 
				
			||||||
 | 
										sceneContext.getStage().setIconified(true);
 | 
				
			||||||
 | 
										e.consume();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Initialize status tray icon
 | 
				
			||||||
 | 
								final var trayIcon = new StatusTrayIcon(sceneContext.getStage());
 | 
				
			||||||
 | 
								settings.getItems().get("hideOnClose").setChangeHandler(c -> {
 | 
				
			||||||
 | 
									if ((Boolean) c) trayIcon.show();
 | 
				
			||||||
 | 
									else trayIcon.hide();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import javafx.fxml.FXML;
 | 
				
			|||||||
import javafx.scene.control.*;
 | 
					import javafx.scene.control.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.ui.SceneContext;
 | 
					import envoy.client.ui.SceneContext;
 | 
				
			||||||
 | 
					import envoy.client.ui.settings.DownloadSettingsPane;
 | 
				
			||||||
import envoy.client.ui.settings.GeneralSettingsPane;
 | 
					import envoy.client.ui.settings.GeneralSettingsPane;
 | 
				
			||||||
import envoy.client.ui.settings.SettingsPane;
 | 
					import envoy.client.ui.settings.SettingsPane;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,7 +30,10 @@ public class SettingsScene {
 | 
				
			|||||||
	 * @param sceneContext enables the user to return to the chat scene
 | 
						 * @param sceneContext enables the user to return to the chat scene
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void initializeData(SceneContext sceneContext) { this.sceneContext = sceneContext; }
 | 
						public void initializeData(SceneContext sceneContext) {
 | 
				
			||||||
 | 
							this.sceneContext = sceneContext;
 | 
				
			||||||
 | 
							settingsList.getItems().add(new DownloadSettingsPane(sceneContext));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void initialize() {
 | 
						private void initialize() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,12 +53,12 @@ public class ChatControl extends HBox {
 | 
				
			|||||||
		getChildren().add(new ContactControl(chat.getRecipient()));
 | 
							getChildren().add(new ContactControl(chat.getRecipient()));
 | 
				
			||||||
		// Unread messages
 | 
							// Unread messages
 | 
				
			||||||
		if (chat.getUnreadAmount() != 0) {
 | 
							if (chat.getUnreadAmount() != 0) {
 | 
				
			||||||
			Region spacing = new Region();
 | 
								final var spacing = new Region();
 | 
				
			||||||
			setHgrow(spacing, Priority.ALWAYS);
 | 
								setHgrow(spacing, Priority.ALWAYS);
 | 
				
			||||||
			getChildren().add(spacing);
 | 
								getChildren().add(spacing);
 | 
				
			||||||
			final var unreadMessagesLabel = new Label(Integer.toString(chat.getUnreadAmount()));
 | 
								final var unreadMessagesLabel = new Label(Integer.toString(chat.getUnreadAmount()));
 | 
				
			||||||
			unreadMessagesLabel.setMinSize(15, 15);
 | 
								unreadMessagesLabel.setMinSize(15, 15);
 | 
				
			||||||
			var vBox2 = new VBox();
 | 
								final var vBox2 = new VBox();
 | 
				
			||||||
			vBox2.setAlignment(Pos.CENTER_RIGHT);
 | 
								vBox2.setAlignment(Pos.CENTER_RIGHT);
 | 
				
			||||||
			unreadMessagesLabel.setAlignment(Pos.CENTER);
 | 
								unreadMessagesLabel.setAlignment(Pos.CENTER);
 | 
				
			||||||
			unreadMessagesLabel.getStyleClass().add("unreadMessagesAmount");
 | 
								unreadMessagesLabel.getStyleClass().add("unreadMessagesAmount");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ package envoy.client.ui.listcell;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.awt.Toolkit;
 | 
					import java.awt.Toolkit;
 | 
				
			||||||
import java.awt.datatransfer.StringSelection;
 | 
					import java.awt.datatransfer.StringSelection;
 | 
				
			||||||
import java.io.ByteArrayInputStream;
 | 
					import java.io.*;
 | 
				
			||||||
import java.time.ZoneId;
 | 
					import java.time.ZoneId;
 | 
				
			||||||
import java.time.format.DateTimeFormatter;
 | 
					import java.time.format.DateTimeFormatter;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
@@ -17,10 +17,14 @@ import javafx.scene.control.MenuItem;
 | 
				
			|||||||
import javafx.scene.image.Image;
 | 
					import javafx.scene.image.Image;
 | 
				
			||||||
import javafx.scene.image.ImageView;
 | 
					import javafx.scene.image.ImageView;
 | 
				
			||||||
import javafx.scene.layout.*;
 | 
					import javafx.scene.layout.*;
 | 
				
			||||||
 | 
					import javafx.stage.FileChooser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.LocalDB;
 | 
					import envoy.client.data.LocalDB;
 | 
				
			||||||
 | 
					import envoy.client.data.Settings;
 | 
				
			||||||
import envoy.client.ui.AudioControl;
 | 
					import envoy.client.ui.AudioControl;
 | 
				
			||||||
import envoy.client.ui.IconUtil;
 | 
					import envoy.client.ui.IconUtil;
 | 
				
			||||||
 | 
					import envoy.client.ui.SceneContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.data.GroupMessage;
 | 
					import envoy.data.GroupMessage;
 | 
				
			||||||
import envoy.data.Message;
 | 
					import envoy.data.Message;
 | 
				
			||||||
import envoy.data.Message.MessageStatus;
 | 
					import envoy.data.Message.MessageStatus;
 | 
				
			||||||
@@ -44,9 +48,12 @@ public class MessageControl extends Label {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private static LocalDB localDB;
 | 
						private static LocalDB localDB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static SceneContext sceneContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final DateTimeFormatter			dateFormat		= DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
 | 
						private static final DateTimeFormatter			dateFormat		= DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss")
 | 
				
			||||||
		.withZone(ZoneId.systemDefault());
 | 
							.withZone(ZoneId.systemDefault());
 | 
				
			||||||
	private static final Map<MessageStatus, Image>	statusImages	= IconUtil.loadByEnum(MessageStatus.class, 16);
 | 
						private static final Map<MessageStatus, Image>	statusImages	= IconUtil.loadByEnum(MessageStatus.class, 16);
 | 
				
			||||||
 | 
						private static final Settings					settings		= Settings.getInstance();
 | 
				
			||||||
	private static final Logger						logger			= EnvoyLog.getLogger(MessageControl.class);
 | 
						private static final Logger						logger			= EnvoyLog.getLogger(MessageControl.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -149,7 +156,26 @@ public class MessageControl extends Label {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private void loadMessageInfoScene(Message message) { logger.log(Level.FINEST, "message info scene was requested for " + message); }
 | 
						private void loadMessageInfoScene(Message message) { logger.log(Level.FINEST, "message info scene was requested for " + message); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void saveAttachment(Message message) { logger.log(Level.FINEST, "attachment saving was requested for " + message); }
 | 
						private void saveAttachment(Message message) {
 | 
				
			||||||
 | 
							File		file;
 | 
				
			||||||
 | 
							final var	fileName			= message.getAttachment().getName();
 | 
				
			||||||
 | 
							final var	downloadLocation	= settings.getDownloadLocation();
 | 
				
			||||||
 | 
							// Show save file dialog, if the user did not opt-out
 | 
				
			||||||
 | 
							if (!settings.isDownloadSavedWithoutAsking()) {
 | 
				
			||||||
 | 
								final var fileChooser = new FileChooser();
 | 
				
			||||||
 | 
								fileChooser.setInitialFileName(fileName);
 | 
				
			||||||
 | 
								fileChooser.setInitialDirectory(downloadLocation);
 | 
				
			||||||
 | 
								file = fileChooser.showSaveDialog(sceneContext.getStage());
 | 
				
			||||||
 | 
							} else file = new File(downloadLocation, fileName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// A file was selected
 | 
				
			||||||
 | 
							if (file != null) try (FileOutputStream fos = new FileOutputStream(file)) {
 | 
				
			||||||
 | 
								fos.write(message.getAttachment().getData());
 | 
				
			||||||
 | 
								logger.log(Level.FINE, "Attachment of message was saved at " + file.getAbsolutePath());
 | 
				
			||||||
 | 
							} catch (final IOException e) {
 | 
				
			||||||
 | 
								logger.log(Level.WARNING, "Could not save attachment of " + message + ": ", e);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param localDB the localDB
 | 
						 * @param localDB the localDB
 | 
				
			||||||
@@ -163,4 +189,10 @@ public class MessageControl extends Label {
 | 
				
			|||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean isOwnMessage() { return ownMessage; }
 | 
						public boolean isOwnMessage() { return ownMessage; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param sceneContext the scene context storing the stage used in Envoy
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static void setSceneContext(SceneContext sceneContext) { MessageControl.sceneContext = sceneContext; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					package envoy.client.ui.settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javafx.geometry.Insets;
 | 
				
			||||||
 | 
					import javafx.scene.control.Button;
 | 
				
			||||||
 | 
					import javafx.scene.control.CheckBox;
 | 
				
			||||||
 | 
					import javafx.scene.control.Label;
 | 
				
			||||||
 | 
					import javafx.scene.layout.HBox;
 | 
				
			||||||
 | 
					import javafx.scene.layout.VBox;
 | 
				
			||||||
 | 
					import javafx.stage.DirectoryChooser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.ui.SceneContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Displays options for downloading {@link envoy.data.Attachment}s.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>DownloadSettingsPane.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>27.07.2020</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class DownloadSettingsPane extends SettingsPane {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Constructs a new {@code DownloadSettingsPane}.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param sceneContext the {@code SceneContext} used to block input to the
 | 
				
			||||||
 | 
						 *                     {@link javafx.stage.Stage} used in Envoy
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public DownloadSettingsPane(SceneContext sceneContext) {
 | 
				
			||||||
 | 
							super("Download");
 | 
				
			||||||
 | 
							final var vbox = new VBox(15);
 | 
				
			||||||
 | 
							vbox.setPadding(new Insets(15));
 | 
				
			||||||
 | 
							// checkbox to disable asking
 | 
				
			||||||
 | 
							final var checkBox = new CheckBox(settings.getItems().get("autoSaveDownloads").getUserFriendlyName());
 | 
				
			||||||
 | 
							checkBox.setSelected(settings.isDownloadSavedWithoutAsking());
 | 
				
			||||||
 | 
							checkBox.setOnAction(e -> settings.setDownloadSavedWithoutAsking(checkBox.isSelected()));
 | 
				
			||||||
 | 
							vbox.getChildren().add(checkBox);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Displaying the default path to save to
 | 
				
			||||||
 | 
							vbox.getChildren().add(new Label(settings.getItems().get("downloadLocation").getDescription() + ":"));
 | 
				
			||||||
 | 
							final var	hbox		= new HBox(20);
 | 
				
			||||||
 | 
							final var	currentPath	= new Label(settings.getDownloadLocation().getAbsolutePath());
 | 
				
			||||||
 | 
							hbox.getChildren().add(currentPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Setting the default path
 | 
				
			||||||
 | 
							final var button = new Button("Select");
 | 
				
			||||||
 | 
							button.setOnAction(e -> {
 | 
				
			||||||
 | 
								final var directoryChooser = new DirectoryChooser();
 | 
				
			||||||
 | 
								directoryChooser.setTitle("Select the directory where attachments should be saved to");
 | 
				
			||||||
 | 
								directoryChooser.setInitialDirectory(settings.getDownloadLocation());
 | 
				
			||||||
 | 
								final var selectedDirectory = directoryChooser.showDialog(sceneContext.getStage());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (selectedDirectory != null) {
 | 
				
			||||||
 | 
									currentPath.setText(selectedDirectory.getAbsolutePath());
 | 
				
			||||||
 | 
									settings.setDownloadLocation(selectedDirectory);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							hbox.getChildren().add(button);
 | 
				
			||||||
 | 
							vbox.getChildren().add(hbox);
 | 
				
			||||||
 | 
							getChildren().add(vbox);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -5,7 +5,6 @@ import java.util.List;
 | 
				
			|||||||
import javafx.scene.control.ComboBox;
 | 
					import javafx.scene.control.ComboBox;
 | 
				
			||||||
import javafx.scene.layout.VBox;
 | 
					import javafx.scene.layout.VBox;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Settings;
 | 
					 | 
				
			||||||
import envoy.client.data.SettingsItem;
 | 
					import envoy.client.data.SettingsItem;
 | 
				
			||||||
import envoy.client.event.ThemeChangeEvent;
 | 
					import envoy.client.event.ThemeChangeEvent;
 | 
				
			||||||
import envoy.data.User.UserStatus;
 | 
					import envoy.data.User.UserStatus;
 | 
				
			||||||
@@ -21,8 +20,6 @@ import envoy.event.EventBus;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public class GeneralSettingsPane extends SettingsPane {
 | 
					public class GeneralSettingsPane extends SettingsPane {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final Settings settings = Settings.getInstance();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -31,7 +28,7 @@ public class GeneralSettingsPane extends SettingsPane {
 | 
				
			|||||||
		final var vbox = new VBox();
 | 
							final var vbox = new VBox();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO: Support other value types
 | 
							// TODO: Support other value types
 | 
				
			||||||
		List.of("onCloseMode", "enterToSend")
 | 
							List.of("hideOnClose", "enterToSend")
 | 
				
			||||||
			.stream()
 | 
								.stream()
 | 
				
			||||||
			.map(settings.getItems()::get)
 | 
								.map(settings.getItems()::get)
 | 
				
			||||||
			.map(i -> new SettingsCheckbox((SettingsItem<Boolean>) i))
 | 
								.map(i -> new SettingsCheckbox((SettingsItem<Boolean>) i))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,8 @@ package envoy.client.ui.settings;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import javafx.scene.layout.Pane;
 | 
					import javafx.scene.layout.Pane;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.data.Settings;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>envoy-client</strong><br>
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
 * File: <strong>SettingsPane.java</strong><br>
 | 
					 * File: <strong>SettingsPane.java</strong><br>
 | 
				
			||||||
@@ -14,6 +16,8 @@ public abstract class SettingsPane extends Pane {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	protected String title;
 | 
						protected String title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected static final Settings settings = Settings.getInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected SettingsPane(String title) { this.title = title; }
 | 
						protected SettingsPane(String title) { this.title = title; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -24,46 +24,5 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		<!-- Disable resource folder -->
 | 
							<!-- Disable resource folder -->
 | 
				
			||||||
		<resources />
 | 
							<resources />
 | 
				
			||||||
 | 
					 | 
				
			||||||
		<pluginManagement>
 | 
					 | 
				
			||||||
			<plugins>
 | 
					 | 
				
			||||||
				<plugin>
 | 
					 | 
				
			||||||
					<groupId>org.apache.maven.plugins</groupId>
 | 
					 | 
				
			||||||
					<artifactId>maven-compiler-plugin</artifactId>
 | 
					 | 
				
			||||||
					<version>3.8.1</version>
 | 
					 | 
				
			||||||
				</plugin>
 | 
					 | 
				
			||||||
			</plugins>
 | 
					 | 
				
			||||||
		</pluginManagement>
 | 
					 | 
				
			||||||
		<plugins>
 | 
					 | 
				
			||||||
			<plugin>
 | 
					 | 
				
			||||||
				<groupId>org.apache.maven.plugins</groupId>
 | 
					 | 
				
			||||||
				<artifactId>maven-source-plugin</artifactId>
 | 
					 | 
				
			||||||
				<version>3.2.1</version>
 | 
					 | 
				
			||||||
				<executions>
 | 
					 | 
				
			||||||
					<execution>
 | 
					 | 
				
			||||||
						<id>attach-sources</id>
 | 
					 | 
				
			||||||
						<goals>
 | 
					 | 
				
			||||||
							<goal>jar</goal>
 | 
					 | 
				
			||||||
						</goals>
 | 
					 | 
				
			||||||
					</execution>
 | 
					 | 
				
			||||||
				</executions>
 | 
					 | 
				
			||||||
			</plugin>
 | 
					 | 
				
			||||||
			<plugin>
 | 
					 | 
				
			||||||
				<groupId>org.apache.maven.plugins</groupId>
 | 
					 | 
				
			||||||
				<artifactId>maven-javadoc-plugin</artifactId>
 | 
					 | 
				
			||||||
				<version>3.1.1</version>
 | 
					 | 
				
			||||||
				<executions>
 | 
					 | 
				
			||||||
					<execution>
 | 
					 | 
				
			||||||
						<id>attach-javadocs</id>
 | 
					 | 
				
			||||||
						<goals>
 | 
					 | 
				
			||||||
							<goal>jar</goal>
 | 
					 | 
				
			||||||
						</goals>
 | 
					 | 
				
			||||||
					</execution>
 | 
					 | 
				
			||||||
				</executions>
 | 
					 | 
				
			||||||
				<configuration>
 | 
					 | 
				
			||||||
					<doclint>none</doclint>
 | 
					 | 
				
			||||||
				</configuration>
 | 
					 | 
				
			||||||
			</plugin>
 | 
					 | 
				
			||||||
		</plugins>
 | 
					 | 
				
			||||||
	</build>
 | 
						</build>
 | 
				
			||||||
</project>
 | 
					</project>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,7 +30,6 @@ public class Attachment implements Serializable {
 | 
				
			|||||||
		 */
 | 
							 */
 | 
				
			||||||
		PICTURE,
 | 
							PICTURE,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
		/**
 | 
							/**
 | 
				
			||||||
		 * This attachment type denotes a video.
 | 
							 * This attachment type denotes a video.
 | 
				
			||||||
		 *
 | 
							 *
 | 
				
			||||||
@@ -55,18 +54,21 @@ public class Attachment implements Serializable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private final byte[]			data;
 | 
						private final byte[]			data;
 | 
				
			||||||
	private final AttachmentType	type;
 | 
						private final AttachmentType	type;
 | 
				
			||||||
 | 
						private final String			name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final long serialVersionUID = 1L;
 | 
						private static final long serialVersionUID = 2L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Constructs an attachment.
 | 
						 * Constructs an attachment.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param data the data of the attachment
 | 
						 * @param data the data of the attachment
 | 
				
			||||||
 | 
						 * @param name the name of the attachment
 | 
				
			||||||
	 * @param type the type of the attachment
 | 
						 * @param type the type of the attachment
 | 
				
			||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Attachment(byte[] data, AttachmentType type) {
 | 
						public Attachment(byte[] data, String name, AttachmentType type) {
 | 
				
			||||||
		this.data	= data;
 | 
							this.data	= data;
 | 
				
			||||||
 | 
							this.name	= name;
 | 
				
			||||||
		this.type	= type;
 | 
							this.type	= type;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -81,4 +83,10 @@ public class Attachment implements Serializable {
 | 
				
			|||||||
	 * @since Envoy Common v0.1-beta
 | 
						 * @since Envoy Common v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public AttachmentType getType() { return type; }
 | 
						public AttachmentType getType() { return type; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the name
 | 
				
			||||||
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public String getName() { return name; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										45
									
								
								common/src/main/java/envoy/event/IsTyping.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								common/src/main/java/envoy/event/IsTyping.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					package envoy.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This event should be sent when a user is currently typing something in a
 | 
				
			||||||
 | 
					 * chat.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>IsTyping.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>24.07.2020</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class IsTyping extends Event<Long> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final long destinationID;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * The number of milliseconds that this event will be active.<br>
 | 
				
			||||||
 | 
						 * Currently set to 3.5 seconds.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static final int millisecondsActive = 3500;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Creates a new {@code IsTyping} event with originator and recipient.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param sourceID      the id of the originator
 | 
				
			||||||
 | 
						 * @param destinationID the id of the contact the user wrote to
 | 
				
			||||||
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public IsTyping(Long sourceID, long destinationID) {
 | 
				
			||||||
 | 
							super(sourceID);
 | 
				
			||||||
 | 
							this.destinationID = destinationID;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the id of the contact in whose chat the user typed something
 | 
				
			||||||
 | 
						 * @since Envoy Common v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public long getDestinationID() { return destinationID; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										12
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								pom.xml
									
									
									
									
									
								
							@@ -18,6 +18,18 @@
 | 
				
			|||||||
		<maven.compiler.target>11</maven.compiler.target>
 | 
							<maven.compiler.target>11</maven.compiler.target>
 | 
				
			||||||
	</properties>
 | 
						</properties>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<build>
 | 
				
			||||||
 | 
							<pluginManagement>
 | 
				
			||||||
 | 
								<plugins>
 | 
				
			||||||
 | 
									<plugin>
 | 
				
			||||||
 | 
										<groupId>org.apache.maven.plugins</groupId>
 | 
				
			||||||
 | 
										<artifactId>maven-compiler-plugin</artifactId>
 | 
				
			||||||
 | 
										<version>3.8.1</version>
 | 
				
			||||||
 | 
									</plugin>
 | 
				
			||||||
 | 
								</plugins>
 | 
				
			||||||
 | 
							</pluginManagement>
 | 
				
			||||||
 | 
						</build>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<modules>
 | 
						<modules>
 | 
				
			||||||
		<module>common</module>
 | 
							<module>common</module>
 | 
				
			||||||
		<module>client</module>
 | 
							<module>client</module>
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -49,15 +49,6 @@
 | 
				
			|||||||
				<directory>src/main/resources</directory>
 | 
									<directory>src/main/resources</directory>
 | 
				
			||||||
			</resource>
 | 
								</resource>
 | 
				
			||||||
		</resources>
 | 
							</resources>
 | 
				
			||||||
		<pluginManagement>
 | 
					 | 
				
			||||||
			<plugins>
 | 
					 | 
				
			||||||
				<plugin>
 | 
					 | 
				
			||||||
					<groupId>org.apache.maven.plugins</groupId>
 | 
					 | 
				
			||||||
					<artifactId>maven-compiler-plugin</artifactId>
 | 
					 | 
				
			||||||
					<version>3.8.1</version>
 | 
					 | 
				
			||||||
				</plugin>
 | 
					 | 
				
			||||||
			</plugins>
 | 
					 | 
				
			||||||
		</pluginManagement>
 | 
					 | 
				
			||||||
		<plugins>
 | 
							<plugins>
 | 
				
			||||||
			<plugin>
 | 
								<plugin>
 | 
				
			||||||
				<groupId>org.apache.maven.plugins</groupId>
 | 
									<groupId>org.apache.maven.plugins</groupId>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,7 +69,8 @@ public class Startup {
 | 
				
			|||||||
						new UserStatusChangeProcessor(),
 | 
											new UserStatusChangeProcessor(),
 | 
				
			||||||
						new IDGeneratorRequestProcessor(),
 | 
											new IDGeneratorRequestProcessor(),
 | 
				
			||||||
						new UserSearchProcessor(),
 | 
											new UserSearchProcessor(),
 | 
				
			||||||
						new ContactOperationProcessor())));
 | 
											new ContactOperationProcessor(),
 | 
				
			||||||
 | 
											new IsTypingProcessor())));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Initialize the current message ID
 | 
							// Initialize the current message ID
 | 
				
			||||||
		final PersistenceManager persistenceManager = PersistenceManager.getInstance();
 | 
							final PersistenceManager persistenceManager = PersistenceManager.getInstance();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -76,6 +76,7 @@ public class Message {
 | 
				
			|||||||
	protected envoy.data.Message.MessageStatus	status;
 | 
						protected envoy.data.Message.MessageStatus	status;
 | 
				
			||||||
	protected AttachmentType					attachmentType;
 | 
						protected AttachmentType					attachmentType;
 | 
				
			||||||
	protected byte[]							attachment;
 | 
						protected byte[]							attachment;
 | 
				
			||||||
 | 
						protected String							attachmentName;
 | 
				
			||||||
	protected boolean							forwarded;
 | 
						protected boolean							forwarded;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -93,7 +94,7 @@ public class Message {
 | 
				
			|||||||
	 * @since Envoy Server Standalone v0.1-alpha
 | 
						 * @since Envoy Server Standalone v0.1-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Message(envoy.data.Message message) {
 | 
						public Message(envoy.data.Message message) {
 | 
				
			||||||
		PersistenceManager persistenceManager = PersistenceManager.getInstance();
 | 
							final var persistenceManager = PersistenceManager.getInstance();
 | 
				
			||||||
		id				= message.getID();
 | 
							id				= message.getID();
 | 
				
			||||||
		status			= message.getStatus();
 | 
							status			= message.getStatus();
 | 
				
			||||||
		text			= message.getText();
 | 
							text			= message.getText();
 | 
				
			||||||
@@ -104,8 +105,10 @@ public class Message {
 | 
				
			|||||||
		recipient		= persistenceManager.getContactByID(message.getRecipientID());
 | 
							recipient		= persistenceManager.getContactByID(message.getRecipientID());
 | 
				
			||||||
		forwarded		= message.isForwarded();
 | 
							forwarded		= message.isForwarded();
 | 
				
			||||||
		if (message.hasAttachment()) {
 | 
							if (message.hasAttachment()) {
 | 
				
			||||||
			attachment		= message.getAttachment().getData();
 | 
								final var messageAttachment = message.getAttachment();
 | 
				
			||||||
			attachmentType	= message.getAttachment().getType();
 | 
								attachment		= messageAttachment.getData();
 | 
				
			||||||
 | 
								attachmentName	= messageAttachment.getName();
 | 
				
			||||||
 | 
								attachmentType	= messageAttachment.getType();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,13 +126,13 @@ public class Message {
 | 
				
			|||||||
	 * @since Envoy Server Standalone v0.1-beta
 | 
						 * @since Envoy Server Standalone v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	MessageBuilder prepareBuilder() {
 | 
						MessageBuilder prepareBuilder() {
 | 
				
			||||||
		var builder = new MessageBuilder(sender.getID(), recipient.getID(), id).setText(text)
 | 
							final var builder = new MessageBuilder(sender.getID(), recipient.getID(), id).setText(text)
 | 
				
			||||||
			.setCreationDate(creationDate)
 | 
								.setCreationDate(creationDate)
 | 
				
			||||||
			.setReceivedDate(receivedDate)
 | 
								.setReceivedDate(receivedDate)
 | 
				
			||||||
			.setReadDate(readDate)
 | 
								.setReadDate(readDate)
 | 
				
			||||||
			.setStatus(status)
 | 
								.setStatus(status)
 | 
				
			||||||
			.setForwarded(forwarded);
 | 
								.setForwarded(forwarded);
 | 
				
			||||||
		if (attachment != null) builder.setAttachment(new Attachment(attachment, attachmentType));
 | 
							if (attachment != null) builder.setAttachment(new Attachment(attachment, attachmentName, attachmentType));
 | 
				
			||||||
		return builder;
 | 
							return builder;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -282,6 +285,18 @@ public class Message {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setAttachmentType(AttachmentType attachmentType) { this.attachmentType = attachmentType; }
 | 
						public void setAttachmentType(AttachmentType attachmentType) { this.attachmentType = attachmentType; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the attachmentName
 | 
				
			||||||
 | 
						 * @since Envoy Server v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public String getAttachmentName() { return attachmentName; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param attachmentName the attachmentName to set
 | 
				
			||||||
 | 
						 * @since Envoy Server v0.2-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void setAttachmentName(String attachmentName) { this.attachmentName = attachmentName; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return whether this message is a forwarded message
 | 
						 * @return whether this message is a forwarded message
 | 
				
			||||||
	 * @since Envoy Server Standalone v0.1-alpha
 | 
						 * @since Envoy Server Standalone v0.1-alpha
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ package envoy.server.net;
 | 
				
			|||||||
import java.io.ByteArrayInputStream;
 | 
					import java.io.ByteArrayInputStream;
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.io.ObjectInputStream;
 | 
					import java.io.ObjectInputStream;
 | 
				
			||||||
 | 
					import java.lang.reflect.ParameterizedType;
 | 
				
			||||||
import java.util.Set;
 | 
					import java.util.Set;
 | 
				
			||||||
import java.util.logging.Level;
 | 
					import java.util.logging.Level;
 | 
				
			||||||
import java.util.logging.Logger;
 | 
					import java.util.logging.Logger;
 | 
				
			||||||
@@ -50,14 +51,19 @@ public class ObjectMessageProcessor implements IMessageProcessor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			logger.fine("Received " + obj);
 | 
								logger.fine("Received " + obj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Process object
 | 
								// Get processor and input class and process object
 | 
				
			||||||
			processors.stream().filter(p -> p.getInputClass().equals(obj.getClass())).forEach((@SuppressWarnings("rawtypes") ObjectProcessor p) -> {
 | 
								for (@SuppressWarnings("rawtypes")
 | 
				
			||||||
 | 
								ObjectProcessor p : processors) {
 | 
				
			||||||
 | 
									Class<?> c = (Class<?>) ((ParameterizedType) p.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
 | 
				
			||||||
 | 
									if (c.equals(obj.getClass())) {
 | 
				
			||||||
					try {
 | 
										try {
 | 
				
			||||||
					p.process(p.getInputClass().cast(obj), message.socketId, new ObjectWriteProxy(writeProxy));
 | 
											p.process(c.cast(obj), message.socketId, new ObjectWriteProxy(writeProxy));
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
					} catch (IOException e) {
 | 
										} catch (IOException e) {
 | 
				
			||||||
						logger.log(Level.SEVERE, "Exception during processor execution: ", e);
 | 
											logger.log(Level.SEVERE, "Exception during processor execution: ", e);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
			});
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		} catch (IOException | ClassNotFoundException e) {
 | 
							} catch (IOException | ClassNotFoundException e) {
 | 
				
			||||||
			e.printStackTrace();
 | 
								e.printStackTrace();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,7 +41,4 @@ public class ContactOperationProcessor implements ObjectProcessor<ContactOperati
 | 
				
			|||||||
				logger.warning(String.format("Received %s with an unsupported operation.", evt));
 | 
									logger.warning(String.format("Received %s with an unsupported operation.", evt));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<ContactOperation> getInputClass() { return ContactOperation.class; }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,7 +40,4 @@ public class GroupCreationProcessor implements ObjectProcessor<GroupCreation> {
 | 
				
			|||||||
			.map(connectionManager::getSocketID)
 | 
								.map(connectionManager::getSocketID)
 | 
				
			||||||
			.forEach(memberSocketID -> writeProxy.write(memberSocketID, new ContactOperation(group.toCommon(), ElementOperation.ADD)));
 | 
								.forEach(memberSocketID -> writeProxy.write(memberSocketID, new ContactOperation(group.toCommon(), ElementOperation.ADD)));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<GroupCreation> getInputClass() { return GroupCreation.class; }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,7 +61,4 @@ public class GroupMessageProcessor implements ObjectProcessor<GroupMessage> {
 | 
				
			|||||||
			logger.warning("Received a groupMessage with an ID that already exists");
 | 
								logger.warning("Received a groupMessage with an ID that already exists");
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<GroupMessage> getInputClass() { return GroupMessage.class; }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -63,7 +63,4 @@ public class GroupMessageStatusChangeProcessor implements ObjectProcessor<GroupM
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		persistenceManager.updateMessage(gmsg);
 | 
							persistenceManager.updateMessage(gmsg);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<GroupMessageStatusChange> getInputClass() { return GroupMessageStatusChange.class; }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,7 +47,4 @@ public class GroupResizeProcessor implements ObjectProcessor<GroupResize> {
 | 
				
			|||||||
			.map(connectionManager::getSocketID)
 | 
								.map(connectionManager::getSocketID)
 | 
				
			||||||
			.forEach(memberSocketID -> writeProxy.write(memberSocketID, commonGroup));
 | 
								.forEach(memberSocketID -> writeProxy.write(memberSocketID, commonGroup));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<GroupResize> getInputClass() { return GroupResize.class; }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,9 +21,6 @@ public class IDGeneratorRequestProcessor implements ObjectProcessor<IDGeneratorR
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private static final long ID_RANGE = 200;
 | 
						private static final long ID_RANGE = 200;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<IDGeneratorRequest> getInputClass() { return IDGeneratorRequest.class; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void process(IDGeneratorRequest input, long socketID, ObjectWriteProxy writeProxy) throws IOException {
 | 
						public void process(IDGeneratorRequest input, long socketID, ObjectWriteProxy writeProxy) throws IOException {
 | 
				
			||||||
		writeProxy.write(socketID, createIDGenerator());
 | 
							writeProxy.write(socketID, createIDGenerator());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					package envoy.server.processors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.event.IsTyping;
 | 
				
			||||||
 | 
					import envoy.server.data.PersistenceManager;
 | 
				
			||||||
 | 
					import envoy.server.data.User;
 | 
				
			||||||
 | 
					import envoy.server.net.ConnectionManager;
 | 
				
			||||||
 | 
					import envoy.server.net.ObjectWriteProxy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This processor handles incoming {@link IsTyping} events.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * Project: <strong>envoy-server-standalone</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>IsTypingProcessor.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>24.07.2020</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since Envoy Server v0.2-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class IsTypingProcessor implements ObjectProcessor<IsTyping> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final ConnectionManager	connectionManager	= ConnectionManager.getInstance();
 | 
				
			||||||
 | 
						private static final PersistenceManager	persistenceManager	= PersistenceManager.getInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void process(IsTyping event, long socketID, ObjectWriteProxy writeProxy) throws IOException {
 | 
				
			||||||
 | 
							final var contact = persistenceManager.getContactByID(event.get());
 | 
				
			||||||
 | 
							if (contact instanceof User) {
 | 
				
			||||||
 | 
								final var destinationID = event.getDestinationID();
 | 
				
			||||||
 | 
								if (connectionManager.isOnline(destinationID)) writeProxy.write(connectionManager.getSocketID(destinationID), event);
 | 
				
			||||||
 | 
							} else writeProxy.writeToOnlineContacts(contact.getContacts(), event);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -191,7 +191,4 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
 | 
				
			|||||||
		// Complete the handshake
 | 
							// Complete the handshake
 | 
				
			||||||
		writeProxy.write(socketID, user.toCommon());
 | 
							writeProxy.write(socketID, user.toCommon());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<LoginCredentials> getInputClass() { return LoginCredentials.class; }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,7 +58,4 @@ public class MessageProcessor implements ObjectProcessor<Message> {
 | 
				
			|||||||
			logger.log(Level.WARNING, "Received " + message + " with an ID that already exists!");
 | 
								logger.log(Level.WARNING, "Received " + message + " with an ID that already exists!");
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<Message> getInputClass() { return Message.class; }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,7 +42,4 @@ public class MessageStatusChangeProcessor implements ObjectProcessor<MessageStat
 | 
				
			|||||||
		final long senderID = msg.getSender().getID();
 | 
							final long senderID = msg.getSender().getID();
 | 
				
			||||||
		if (connectionManager.isOnline(senderID)) writeProxy.write(connectionManager.getSocketID(senderID), statusChange);
 | 
							if (connectionManager.isOnline(senderID)) writeProxy.write(connectionManager.getSocketID(senderID), statusChange);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<MessageStatusChange> getInputClass() { return MessageStatusChange.class; }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,4 @@ public class NameChangeProcessor implements ObjectProcessor<NameChange> {
 | 
				
			|||||||
		// Notify online contacts of the name change
 | 
							// Notify online contacts of the name change
 | 
				
			||||||
		writeProxy.writeToOnlineContacts(toUpdate.getContacts(), nameChange);
 | 
							writeProxy.writeToOnlineContacts(toUpdate.getContacts(), nameChange);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<NameChange> getInputClass() { return NameChange.class; }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,12 +18,6 @@ import envoy.server.net.ObjectWriteProxy;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public interface ObjectProcessor<T> {
 | 
					public interface ObjectProcessor<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * @return the class of the request object
 | 
					 | 
				
			||||||
	 * @since Envoy Server Standalone v0.1-alpha
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	Class<T> getInputClass();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param input      the request object
 | 
						 * @param input      the request object
 | 
				
			||||||
	 * @param socketID   the ID of the socket from which the object was received
 | 
						 * @param socketID   the ID of the socket from which the object was received
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,7 +38,4 @@ public class UserSearchProcessor implements ObjectProcessor<UserSearchRequest> {
 | 
				
			|||||||
					.map(User::toCommon)
 | 
										.map(User::toCommon)
 | 
				
			||||||
					.collect(Collectors.toList())));
 | 
										.collect(Collectors.toList())));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<UserSearchRequest> getInputClass() { return UserSearchRequest.class; }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,9 +26,6 @@ public class UserStatusChangeProcessor implements ObjectProcessor<UserStatusChan
 | 
				
			|||||||
	private static final PersistenceManager	persistenceManager	= PersistenceManager.getInstance();
 | 
						private static final PersistenceManager	persistenceManager	= PersistenceManager.getInstance();
 | 
				
			||||||
	private static final Logger				logger				= EnvoyLog.getLogger(UserStatusChangeProcessor.class);
 | 
						private static final Logger				logger				= EnvoyLog.getLogger(UserStatusChangeProcessor.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
					 | 
				
			||||||
	public Class<UserStatusChange> getInputClass() { return UserStatusChange.class; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void process(UserStatusChange input, long socketID, ObjectWriteProxy writeProxy) {
 | 
						public void process(UserStatusChange input, long socketID, ObjectWriteProxy writeProxy) {
 | 
				
			||||||
		// new status should not equal old status
 | 
							// new status should not equal old status
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user