Compare commits
	
		
			25 Commits
		
	
	
		
			f/fancy-di
			...
			05ed5da41b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 05ed5da41b | |||
| c5f4969666   | |||
| 1a9f9a85ab | |||
| 544210a811   | |||
| 5ef5d96445 | |||
| dcf1b0c58d   | |||
| 10213a0d3d   | |||
| b653652f6d | |||
| 0ff910ebde   | |||
| 6d85e337d2 | |||
| 67ebc6be83 | |||
| e3052a2133 | |||
| 4d4865570d | |||
| 0ce8b0c89d | |||
| cd7793a589   | |||
| e5659c1da1 | |||
| f67ca1d61d | |||
| 8bdd201b28 | |||
| f6c772a655   | |||
| 7a883861be | |||
| d4c7813c97 | |||
| 889e9b186f   | |||
| fccd7e70b1 | |||
| 2eeb55ed52 | |||
| 44d3082958 | 
							
								
								
									
										37
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								Jenkinsfile
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| pipeline { | ||||
| 	agent any | ||||
|  | ||||
| 	options { | ||||
| 		ansiColor('xterm') | ||||
| 	} | ||||
|  | ||||
| 	stages { | ||||
| 		stage('Build') { | ||||
| 			steps { | ||||
| 				sh 'mvn -DskipTests clean package' | ||||
| 			} | ||||
| 		} | ||||
| 		stage('Test') { | ||||
| 			steps { | ||||
| 				sh 'mvn test' | ||||
| 			} | ||||
| 			post { | ||||
| 				always { | ||||
| 					junit '*/target/surefire-reports/*.xml' | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		stage('SonarQube Analysis') { | ||||
| 			steps { | ||||
| 				withSonarQubeEnv('KSKE SonarQube') { | ||||
| 					sh 'mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.9.1.2184:sonar' | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	post { | ||||
| 		success { | ||||
| 			archiveArtifacts artifacts: 'client/target/envoy-client-*-shaded.jar, server/target/envoy-server-jar-with-dependencies.jar' | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -17,6 +17,8 @@ If you want to transfer a file to another user, you can attach it to a message. | ||||
|  | ||||
| On the settings page some convenience features can be configured, as well as the color theme. | ||||
|  | ||||
| Additional info on how to use Envoy can be found [here](https://git.kske.dev/zdm/envoy/wiki) in the client section. | ||||
|  | ||||
| ### System requirements | ||||
|  | ||||
| To run Envoy, you have to install a Java Runtime Environment (JRE) of at least version 11. | ||||
| @@ -29,7 +31,7 @@ Most major Linux distributions like Debian, Arch and Gentoo have a Noto emoji pa | ||||
|  | ||||
| To set up an Envoy server, download the package from the release page. | ||||
|  | ||||
| Because the project lacks external documentation for the moment, please refer to the Javadoc inside the source code to configure your Envoy instance. | ||||
| To configure the behavior of Envoy Server, please have a look at the [documentation](https://git.kske.dev/zdm/envoy/wiki), specifically the server part. | ||||
|  | ||||
| ### System requirements | ||||
|  | ||||
|   | ||||
| @@ -21,7 +21,6 @@ | ||||
| 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"> | ||||
| 		<attributes> | ||||
| 			<attribute name="maven.pomderived" value="true"/> | ||||
| 			<attribute name="module" value="true"/> | ||||
| 		</attributes> | ||||
| 	</classpathentry> | ||||
| 	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER"> | ||||
|   | ||||
| @@ -22,20 +22,21 @@ import envoy.client.net.WriteProxy; | ||||
|  */ | ||||
| public class Chat implements Serializable { | ||||
|  | ||||
| 	protected boolean disabled; | ||||
| 	protected transient ObservableList<Message> messages = FXCollections.observableArrayList(); | ||||
|  | ||||
| 	protected final Contact recipient; | ||||
|  | ||||
| 	protected boolean	disabled; | ||||
| 	protected boolean	underlyingContactDeleted; | ||||
|  | ||||
| 	/** | ||||
| 	 * Stores the last time an {@link envoy.event.IsTyping} event has been sent. | ||||
| 	 */ | ||||
| 	protected transient long lastWritingEvent; | ||||
|  | ||||
| 	protected transient ObservableList<Message> messages = FXCollections.observableArrayList(); | ||||
|  | ||||
| 	protected int						unreadAmount; | ||||
| 	protected static IntegerProperty	totalUnreadAmount	= new SimpleIntegerProperty(); | ||||
|  | ||||
| 	protected final Contact recipient; | ||||
|  | ||||
| 	private static final long serialVersionUID = 2L; | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -13,9 +13,8 @@ import java.util.stream.Stream; | ||||
| import javafx.application.Platform; | ||||
| import javafx.collections.*; | ||||
|  | ||||
| import dev.kske.eventbus.Event; | ||||
| import dev.kske.eventbus.EventBus; | ||||
| import dev.kske.eventbus.EventListener; | ||||
| import dev.kske.eventbus.core.*; | ||||
| import dev.kske.eventbus.core.Event; | ||||
|  | ||||
| import envoy.data.*; | ||||
| import envoy.data.Message.MessageStatus; | ||||
| @@ -35,7 +34,7 @@ import envoy.client.event.*; | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since Envoy Client v0.3-alpha | ||||
|  */ | ||||
| public final class LocalDB implements EventListener { | ||||
| public final class LocalDB { | ||||
|  | ||||
| 	// Data | ||||
| 	private User					user; | ||||
| @@ -246,8 +245,13 @@ public final class LocalDB implements EventListener { | ||||
| 	 * @throws IOException if the saving process failed | ||||
| 	 * @since Envoy Client v0.3-alpha | ||||
| 	 */ | ||||
| 	@Event(eventType = EnvoyCloseEvent.class, priority = 500) | ||||
| 	@Event(EnvoyCloseEvent.class) | ||||
| 	@Priority(500) | ||||
| 	private synchronized void save() { | ||||
|  | ||||
| 		// Stop saving if this account has been deleted | ||||
| 		if (userFile == null) | ||||
| 			return; | ||||
| 		EnvoyLog.getLogger(LocalDB.class).log(Level.FINER, "Saving local database..."); | ||||
|  | ||||
| 		// Save users | ||||
| @@ -273,37 +277,66 @@ public final class LocalDB implements EventListener { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 500) | ||||
| 	/** | ||||
| 	 * Deletes any local remnant of this user. | ||||
| 	 * | ||||
| 	 * @since Envoy Client v0.3-beta | ||||
| 	 */ | ||||
| 	public void delete() { | ||||
| 		try { | ||||
|  | ||||
| 			// Save ID generator - can be used for other users in that db | ||||
| 			if (hasIDGenerator()) | ||||
| 				SerializationUtils.write(idGeneratorFile, idGenerator); | ||||
| 		} catch (final IOException e) { | ||||
| 			EnvoyLog.getLogger(LocalDB.class).log(Level.SEVERE, "Unable to save local database: ", | ||||
| 				e); | ||||
| 		} | ||||
| 		if (lastLoginFile != null) | ||||
| 			lastLoginFile.delete(); | ||||
| 		userFile.delete(); | ||||
| 		users.remove(user.getName()); | ||||
| 		userFile = null; | ||||
| 		onLogout(); | ||||
| 	} | ||||
|  | ||||
| 	@Event | ||||
| 	@Priority(500) | ||||
| 	private void onMessage(Message msg) { | ||||
| 		if (msg.getStatus() == MessageStatus.SENT) | ||||
| 			msg.nextStatus(); | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 500) | ||||
| 	@Event | ||||
| 	@Priority(500) | ||||
| 	private void onGroupMessage(GroupMessage msg) { | ||||
| 		// TODO: Cancel event once EventBus is updated | ||||
| 		if (msg.getStatus() == MessageStatus.WAITING || msg.getStatus() == MessageStatus.READ) | ||||
| 			logger.warning("The groupMessage has the unexpected status " + msg.getStatus()); | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 500) | ||||
| 	@Event | ||||
| 	@Priority(500) | ||||
| 	private void onMessageStatusChange(MessageStatusChange evt) { | ||||
| 		getMessage(evt.getID()).ifPresent(msg -> msg.setStatus(evt.get())); | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 500) | ||||
| 	@Event | ||||
| 	@Priority(500) | ||||
| 	private void onGroupMessageStatusChange(GroupMessageStatusChange evt) { | ||||
| 		this.<GroupMessage>getMessage(evt.getID()) | ||||
| 			.ifPresent(msg -> msg.getMemberStatuses().replace(evt.getMemberID(), evt.get())); | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 500) | ||||
| 	@Event | ||||
| 	@Priority(500) | ||||
| 	private void onUserStatusChange(UserStatusChange evt) { | ||||
| 		getChat(evt.getID()).map(Chat::getRecipient).map(User.class::cast) | ||||
| 			.ifPresent(u -> u.setStatus(evt.get())); | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 500) | ||||
| 	@Event | ||||
| 	@Priority(500) | ||||
| 	private void onUserOperation(UserOperation operation) { | ||||
| 		final var eventUser = operation.get(); | ||||
| 		switch (operation.getOperationType()) { | ||||
| @@ -329,13 +362,15 @@ public final class LocalDB implements EventListener { | ||||
| 			Platform.runLater(() -> chats.add(new GroupChat(user, newGroup))); | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 500) | ||||
| 	@Event | ||||
| 	@Priority(500) | ||||
| 	private void onGroupResize(GroupResize evt) { | ||||
| 		getChat(evt.getGroupID()).map(Chat::getRecipient).map(Group.class::cast) | ||||
| 			.ifPresent(evt::apply); | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 500) | ||||
| 	@Event | ||||
| 	@Priority(500) | ||||
| 	private void onNameChange(NameChange evt) { | ||||
| 		chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == evt.getID()).findAny() | ||||
| 			.ifPresent(c -> c.setName(evt.get())); | ||||
| @@ -357,7 +392,8 @@ public final class LocalDB implements EventListener { | ||||
| 	 * | ||||
| 	 * @since Envoy Client v0.2-beta | ||||
| 	 */ | ||||
| 	@Event(eventType = Logout.class, priority = 50) | ||||
| 	@Event(Logout.class) | ||||
| 	@Priority(50) | ||||
| 	private void onLogout() { | ||||
| 		autoSaver.cancel(); | ||||
| 		autoSaveRestart = true; | ||||
| @@ -389,21 +425,33 @@ public final class LocalDB implements EventListener { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 500) | ||||
| 	@Event | ||||
| 	@Priority(500) | ||||
| 	private void onOwnStatusChange(OwnStatusChange statusChange) { | ||||
| 		user.setStatus(statusChange.get()); | ||||
| 	} | ||||
|  | ||||
| 	@Event(eventType = ContactsChangedSinceLastLogin.class, priority = 500) | ||||
| 	@Event(ContactsChangedSinceLastLogin.class) | ||||
| 	@Priority(500) | ||||
| 	private void onContactsChangedSinceLastLogin() { | ||||
| 		contactsChanged = true; | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 500) | ||||
| 	@Event | ||||
| 	@Priority(500) | ||||
| 	private void onContactDisabled(ContactDisabled event) { | ||||
| 		getChat(event.get().getID()).ifPresent(chat -> chat.setDisabled(true)); | ||||
| 	} | ||||
|  | ||||
| 	@Event | ||||
| 	@Priority(500) | ||||
| 	private void onAccountDeletion(AccountDeletion deletion) { | ||||
| 		if (user.getID() == deletion.get()) | ||||
| 			logger.log(Level.WARNING, | ||||
| 				"I have been informed by the server that I have been deleted without even knowing it..."); | ||||
| 		getChat(deletion.get()).ifPresent(chat -> chat.setDisabled(true)); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @return a {@code Map<String, User>} of all users stored locally with their user names as keys | ||||
| 	 * @since Envoy Client v0.2-alpha | ||||
| @@ -461,7 +509,8 @@ public final class LocalDB implements EventListener { | ||||
| 	 * @param idGenerator the message ID generator to set | ||||
| 	 * @since Envoy Client v0.3-alpha | ||||
| 	 */ | ||||
| 	@Event(priority = 150) | ||||
| 	@Event | ||||
| 	@Priority(150) | ||||
| 	public void setIDGenerator(IDGenerator idGenerator) { this.idGenerator = idGenerator; } | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -5,8 +5,7 @@ import java.util.*; | ||||
| import java.util.logging.Level; | ||||
| import java.util.prefs.Preferences; | ||||
|  | ||||
| import dev.kske.eventbus.*; | ||||
| import dev.kske.eventbus.EventListener; | ||||
| import dev.kske.eventbus.core.*; | ||||
|  | ||||
| import envoy.util.*; | ||||
|  | ||||
| @@ -21,7 +20,7 @@ import envoy.client.event.EnvoyCloseEvent; | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since Envoy Client v0.2-alpha | ||||
|  */ | ||||
| public final class Settings implements EventListener { | ||||
| public final class Settings { | ||||
|  | ||||
| 	// Actual settings accessible by the rest of the application | ||||
| 	private Map<String, SettingsItem<?>> items; | ||||
| @@ -69,7 +68,7 @@ public final class Settings implements EventListener { | ||||
| 	 * @throws IOException if an error occurs while saving the themes | ||||
| 	 * @since Envoy Client v0.2-alpha | ||||
| 	 */ | ||||
| 	@Event(eventType = EnvoyCloseEvent.class) | ||||
| 	@Event(EnvoyCloseEvent.class) | ||||
| 	private void save() { | ||||
| 		EnvoyLog.getLogger(Settings.class).log(Level.INFO, "Saving settings..."); | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import envoy.data.User.UserStatus; | ||||
|  | ||||
| import envoy.client.data.Context; | ||||
| import envoy.client.helper.ShutdownHelper; | ||||
| import envoy.client.ui.SceneContext.SceneInfo; | ||||
| import envoy.client.ui.SceneInfo; | ||||
| import envoy.client.util.UserUtil; | ||||
|  | ||||
| /** | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import java.util.*; | ||||
|  | ||||
| import javafx.scene.input.KeyCombination; | ||||
|  | ||||
| import envoy.client.ui.SceneContext.SceneInfo; | ||||
| import envoy.client.ui.SceneInfo; | ||||
|  | ||||
| /** | ||||
|  * Contains all keyboard shortcuts used throughout the application. | ||||
|   | ||||
							
								
								
									
										22
									
								
								client/src/main/java/envoy/client/event/AccountDeletion.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								client/src/main/java/envoy/client/event/AccountDeletion.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| package envoy.client.event; | ||||
|  | ||||
| import envoy.event.Event; | ||||
|  | ||||
| /** | ||||
|  * Signifies the deletion of an account. | ||||
|  *  | ||||
|  * @author Leon Hofmeister | ||||
|  * @since Envoy Common v0.3-beta | ||||
|  */ | ||||
| public class AccountDeletion extends Event<Long> { | ||||
|  | ||||
| 	private static final long serialVersionUID = 1L; | ||||
|  | ||||
| 	/** | ||||
| 	 * @param value the ID of the contact that was deleted | ||||
| 	 * @since Envoy Common v0.3-beta | ||||
| 	 */ | ||||
| 	public AccountDeletion(Long value) { | ||||
| 		super(value); | ||||
| 	} | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| package envoy.client.helper; | ||||
|  | ||||
| import dev.kske.eventbus.EventBus; | ||||
| import dev.kske.eventbus.core.EventBus; | ||||
|  | ||||
| import envoy.client.data.*; | ||||
| import envoy.client.event.EnvoyCloseEvent; | ||||
|   | ||||
| @@ -5,14 +5,14 @@ import java.net.Socket; | ||||
| import java.util.concurrent.TimeoutException; | ||||
| import java.util.logging.*; | ||||
|  | ||||
| import dev.kske.eventbus.*; | ||||
| import dev.kske.eventbus.Event; | ||||
| import dev.kske.eventbus.core.*; | ||||
| import dev.kske.eventbus.core.Event; | ||||
|  | ||||
| import envoy.data.*; | ||||
| import envoy.event.*; | ||||
| import envoy.util.*; | ||||
|  | ||||
| import envoy.client.data.*; | ||||
| import envoy.client.data.ClientConfig; | ||||
| import envoy.client.event.EnvoyCloseEvent; | ||||
|  | ||||
| /** | ||||
| @@ -24,7 +24,7 @@ import envoy.client.event.EnvoyCloseEvent; | ||||
|  * @author Leon Hofmeister | ||||
|  * @since Envoy Client v0.1-alpha | ||||
|  */ | ||||
| public final class Client implements EventListener, Closeable { | ||||
| public final class Client implements Closeable { | ||||
|  | ||||
| 	// Connection handling | ||||
| 	private Socket		socket; | ||||
| @@ -55,13 +55,12 @@ public final class Client implements EventListener, Closeable { | ||||
| 	 * the handshake does exceed this time limit, an exception is thrown. | ||||
| 	 * | ||||
| 	 * @param credentials the login credentials of the user | ||||
| 	 * @param cacheMap    the map of all caches needed | ||||
| 	 * @throws TimeoutException     if the server could not be reached | ||||
| 	 * @throws IOException          if the login credentials could not be written | ||||
| 	 * @throws InterruptedException if the current thread is interrupted while waiting for the | ||||
| 	 *                              handshake response | ||||
| 	 */ | ||||
| 	public void performHandshake(LoginCredentials credentials, CacheMap cacheMap) | ||||
| 	public void performHandshake(LoginCredentials credentials) | ||||
| 		throws TimeoutException, IOException, InterruptedException { | ||||
| 		if (online) | ||||
| 			throw new IllegalStateException("Handshake has already been performed successfully"); | ||||
| @@ -79,7 +78,6 @@ public final class Client implements EventListener, Closeable { | ||||
| 		// Register user creation processor, contact list processor, message cache and | ||||
| 		// authentication token | ||||
| 		receiver.registerProcessor(User.class, sender -> this.sender = sender); | ||||
| 		receiver.registerProcessors(cacheMap.getMap()); | ||||
|  | ||||
| 		// Start receiver | ||||
| 		receiver.start(); | ||||
| @@ -101,42 +99,18 @@ public final class Client implements EventListener, Closeable { | ||||
|  | ||||
| 			if (System.currentTimeMillis() - start > 5000) { | ||||
| 				rejected = true; | ||||
| 				socket.close(); | ||||
| 				receiver.removeAllProcessors(); | ||||
| 				throw new TimeoutException("Did not log in after 5 seconds"); | ||||
| 			} | ||||
| 			Thread.sleep(500); | ||||
| 		} | ||||
|  | ||||
| 		online = true; | ||||
| 		logger.log(Level.INFO, "Handshake completed."); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Initializes the {@link Receiver} used to process data sent from the server to this client. | ||||
| 	 * | ||||
| 	 * @param localDB  the local database used to persist the current {@link IDGenerator} | ||||
| 	 * @param cacheMap the map of all caches needed | ||||
| 	 * @throws IOException if no {@link IDGenerator} is present and none could be requested from the | ||||
| 	 *                     server | ||||
| 	 * @since Envoy Client v0.2-alpha | ||||
| 	 */ | ||||
| 	public void initReceiver(LocalDB localDB, CacheMap cacheMap) throws IOException { | ||||
| 		checkOnline(); | ||||
|  | ||||
| 		// Remove all processors as they are only used during the handshake | ||||
| 		// Remove handshake specific processors | ||||
| 		receiver.removeAllProcessors(); | ||||
|  | ||||
| 		// Relay cached messages and message status changes | ||||
| 		cacheMap.get(Message.class).setProcessor(eventBus::dispatch); | ||||
| 		cacheMap.get(GroupMessage.class).setProcessor(eventBus::dispatch); | ||||
| 		cacheMap.get(MessageStatusChange.class).setProcessor(eventBus::dispatch); | ||||
| 		cacheMap.get(GroupMessageStatusChange.class).setProcessor(eventBus::dispatch); | ||||
|  | ||||
| 		// Request a generator if none is present or the existing one is consumed | ||||
| 		if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext()) | ||||
| 			requestIDGenerator(); | ||||
|  | ||||
| 		// Relay caches | ||||
| 		cacheMap.getMap().values().forEach(Cache::relay); | ||||
| 		online = true; | ||||
| 		logger.log(Level.INFO, "Handshake completed."); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -179,13 +153,15 @@ public final class Client implements EventListener, Closeable { | ||||
| 		send(new IDGeneratorRequest()); | ||||
| 	} | ||||
|  | ||||
| 	@Event(eventType = HandshakeRejection.class, priority = 1000) | ||||
| 	@Event(HandshakeRejection.class) | ||||
| 	@Priority(1000) | ||||
| 	private void onHandshakeRejection() { | ||||
| 		rejected = true; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	@Event(eventType = EnvoyCloseEvent.class, priority = 50) | ||||
| 	@Event(EnvoyCloseEvent.class) | ||||
| 	@Priority(50) | ||||
| 	public void close() { | ||||
| 		if (online) { | ||||
| 			logger.log(Level.INFO, "Closing connection..."); | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import java.util.*; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.logging.*; | ||||
|  | ||||
| import dev.kske.eventbus.*; | ||||
| import dev.kske.eventbus.core.EventBus; | ||||
|  | ||||
| import envoy.util.*; | ||||
|  | ||||
| @@ -87,15 +87,17 @@ public final class Receiver extends Thread { | ||||
| 					// Dispatch to the processor if present | ||||
| 					if (processor != null) | ||||
| 						processor.accept(obj); | ||||
| 					// Dispatch to the event bus if the object is an event without a processor | ||||
| 					else if (obj instanceof IEvent) | ||||
| 						eventBus.dispatch((IEvent) obj); | ||||
| 					// Notify if no processor could be located | ||||
| 					// Dispatch to the event bus if the object has no processor | ||||
| 					else | ||||
| 						logger.log(Level.WARNING, | ||||
| 							String.format( | ||||
| 								"The received object has the %s for which no processor is defined.", | ||||
| 								obj.getClass())); | ||||
| 						eventBus.dispatch(obj); | ||||
|  | ||||
| 					// TODO: Log DeadEvent from Event Bus 1.1.0 | ||||
| 					// Notify if no processor could be located | ||||
| 					// else | ||||
| 					// logger.log(Level.WARNING, | ||||
| 					// String.format( | ||||
| 					// "The received object has the %s for which no processor is defined.", | ||||
| 					// obj.getClass())); | ||||
| 				} | ||||
| 			} catch (final SocketException | EOFException e) { | ||||
| 				// Connection probably closed by client. | ||||
|   | ||||
| @@ -4,12 +4,11 @@ import java.io.IOException; | ||||
| import java.util.Stack; | ||||
| import java.util.logging.Level; | ||||
|  | ||||
| import javafx.application.Platform; | ||||
| import javafx.fxml.FXMLLoader; | ||||
| import javafx.scene.*; | ||||
| import javafx.stage.Stage; | ||||
|  | ||||
| import dev.kske.eventbus.*; | ||||
| import dev.kske.eventbus.core.*; | ||||
|  | ||||
| import envoy.util.EnvoyLog; | ||||
|  | ||||
| @@ -26,53 +25,13 @@ import envoy.client.event.*; | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since Envoy Client v0.1-beta | ||||
|  */ | ||||
| public final class SceneContext implements EventListener { | ||||
|  | ||||
| 	/** | ||||
| 	 * Contains information about different scenes and their FXML resource files. | ||||
| 	 * | ||||
| 	 * @author Kai S. K. Engelbart | ||||
| 	 * @since Envoy Client v0.1-beta | ||||
| 	 */ | ||||
| 	public enum SceneInfo { | ||||
|  | ||||
| 		/** | ||||
| 		 * The main scene in which the chat screen is displayed. | ||||
| 		 * | ||||
| 		 * @since Envoy Client v0.1-beta | ||||
| 		 */ | ||||
| 		CHAT_SCENE("/fxml/ChatScene.fxml"), | ||||
|  | ||||
| 		/** | ||||
| 		 * The scene in which the settings screen is displayed. | ||||
| 		 * | ||||
| 		 * @since Envoy Client v0.1-beta | ||||
| 		 */ | ||||
| 		SETTINGS_SCENE("/fxml/SettingsScene.fxml"), | ||||
|  | ||||
| 		/** | ||||
| 		 * The scene in which the login screen is displayed. | ||||
| 		 * | ||||
| 		 * @since Envoy Client v0.1-beta | ||||
| 		 */ | ||||
| 		LOGIN_SCENE("/fxml/LoginScene.fxml"); | ||||
|  | ||||
| 		/** | ||||
| 		 * The path to the FXML resource. | ||||
| 		 */ | ||||
| 		public final String path; | ||||
|  | ||||
| 		SceneInfo(String path) { | ||||
| 			this.path = path; | ||||
| 		} | ||||
| 	} | ||||
| public final class SceneContext { | ||||
|  | ||||
| 	private final Stage			stage; | ||||
| 	private final FXMLLoader	loader			= new FXMLLoader(); | ||||
| 	private final Stack<Scene>	sceneStack		= new Stack<>(); | ||||
| 	private final Stack<Object>	controllerStack	= new Stack<>(); | ||||
| 	private final Stack<Parent>	roots		= new Stack<>(); | ||||
| 	private final Stack<Object>	controllers	= new Stack<>(); | ||||
|  | ||||
| 	private static final Settings settings = Settings.getInstance(); | ||||
| 	private Scene scene; | ||||
|  | ||||
| 	/** | ||||
| 	 * Initializes the scene context. | ||||
| @@ -88,44 +47,44 @@ public final class SceneContext implements EventListener { | ||||
| 	/** | ||||
| 	 * Loads a new scene specified by a scene info. | ||||
| 	 * | ||||
| 	 * @param sceneInfo specifies the scene to load | ||||
| 	 * @param info specifies the scene to load | ||||
| 	 * @throws RuntimeException if the loading process fails | ||||
| 	 * @since Envoy Client v0.1-beta | ||||
| 	 */ | ||||
| 	public void load(SceneInfo sceneInfo) { | ||||
| 		EnvoyLog.getLogger(SceneContext.class).log(Level.FINER, "Loading scene " + sceneInfo); | ||||
| 		loader.setRoot(null); | ||||
| 		loader.setController(null); | ||||
| 	public void load(SceneInfo info) { | ||||
| 		EnvoyLog.getLogger(SceneContext.class).log(Level.FINER, "Loading scene " + info); | ||||
|  | ||||
| 		try { | ||||
| 			final var	rootNode	= | ||||
| 				(Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path)); | ||||
| 			final var	scene		= new Scene(rootNode); | ||||
| 			final var	controller	= loader.getController(); | ||||
| 			controllerStack.push(controller); | ||||
|  | ||||
| 			sceneStack.push(scene); | ||||
| 			stage.setScene(scene); | ||||
| 			// Load root node and controller | ||||
| 			var		loader		= new FXMLLoader(); | ||||
| 			Parent	root		= loader.load(getClass().getResourceAsStream(info.path)); | ||||
| 			Object	controller	= loader.getController(); | ||||
| 			roots.push(root); | ||||
| 			controllers.push(controller); | ||||
|  | ||||
| 			if (scene == null) { | ||||
|  | ||||
| 				// One-time scene initialization | ||||
| 				scene = new Scene(root, stage.getWidth(), stage.getHeight()); | ||||
| 				applyCSS(); | ||||
| 				stage.setScene(scene); | ||||
| 			} else { | ||||
| 				scene.setRoot(root); | ||||
| 			} | ||||
|  | ||||
| 			// Remove previous keyboard shortcuts | ||||
| 			scene.getAccelerators().clear(); | ||||
|  | ||||
| 			// Supply the global custom keyboard shortcuts for that scene | ||||
| 			scene.getAccelerators() | ||||
| 				.putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(sceneInfo)); | ||||
| 				.putAll(GlobalKeyShortcuts.getInstance().getKeyboardShortcuts(info)); | ||||
|  | ||||
| 			// Supply the scene specific keyboard shortcuts | ||||
| 			if (controller instanceof KeyboardMapping) | ||||
| 				scene.getAccelerators() | ||||
| 					.putAll(((KeyboardMapping) controller).getKeyboardShortcuts()); | ||||
|  | ||||
| 			// The LoginScene is the only scene not intended to be resized | ||||
| 			// As strange as it seems, this is needed as otherwise the LoginScene won't be | ||||
| 			// displayed on some OS (...Debian...) | ||||
| 			stage.sizeToScene(); | ||||
| 			Platform.runLater(() -> stage.setResizable(sceneInfo != SceneInfo.LOGIN_SCENE)); | ||||
| 			applyCSS(); | ||||
| 			stage.show(); | ||||
| 		} catch (final IOException e) { | ||||
| 			EnvoyLog.getLogger(SceneContext.class).log(Level.SEVERE, | ||||
| 				String.format("Could not load scene for %s: ", sceneInfo), e); | ||||
| 		} catch (IOException e) { | ||||
| 			throw new RuntimeException(e); | ||||
| 		} | ||||
| 	} | ||||
| @@ -137,42 +96,45 @@ public final class SceneContext implements EventListener { | ||||
| 	 */ | ||||
| 	public void pop() { | ||||
|  | ||||
| 		// Pop scene and controller | ||||
| 		sceneStack.pop(); | ||||
| 		controllerStack.pop(); | ||||
| 		// Pop current root node and controller | ||||
| 		roots.pop(); | ||||
| 		controllers.pop(); | ||||
|  | ||||
| 		// Apply new scene if present | ||||
| 		if (!sceneStack.isEmpty()) { | ||||
| 			final var newScene = sceneStack.peek(); | ||||
| 			stage.setScene(newScene); | ||||
| 			applyCSS(); | ||||
| 			stage.sizeToScene(); | ||||
| 			// If the controller implements the Restorable interface, | ||||
| 			// the actions to perform on restoration will be executed here | ||||
| 			final var controller = controllerStack.peek(); | ||||
| 		if (!roots.isEmpty()) { | ||||
| 			scene.setRoot(roots.peek()); | ||||
|  | ||||
| 			// Invoke restore if controller is restorable | ||||
| 			var controller = controllers.peek(); | ||||
| 			if (controller instanceof Restorable) | ||||
| 				((Restorable) controller).onRestore(); | ||||
| 		} else { | ||||
|  | ||||
| 			// Remove the current scene entirely | ||||
| 			scene = null; | ||||
| 			stage.setScene(null); | ||||
| 		} | ||||
| 		stage.show(); | ||||
| 	} | ||||
|  | ||||
| 	private void applyCSS() { | ||||
| 		if (!sceneStack.isEmpty()) { | ||||
| 			final var	styleSheets	= stage.getScene().getStylesheets(); | ||||
| 			final var	themeCSS	= "/css/" + settings.getCurrentTheme() + ".css"; | ||||
| 		if (scene != null) { | ||||
| 			var	styleSheets	= scene.getStylesheets(); | ||||
| 			var	themeCSS	= "/css/" + Settings.getInstance().getCurrentTheme() + ".css"; | ||||
| 			styleSheets.clear(); | ||||
| 			styleSheets.addAll(getClass().getResource("/css/base.css").toExternalForm(), | ||||
| 				getClass().getResource(themeCSS).toExternalForm()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@Event(eventType = Logout.class, priority = 150) | ||||
| 	@Event(Logout.class) | ||||
| 	@Priority(150) | ||||
| 	private void onLogout() { | ||||
| 		sceneStack.clear(); | ||||
| 		controllerStack.clear(); | ||||
| 		roots.clear(); | ||||
| 		controllers.clear(); | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 150, eventType = ThemeChangeEvent.class) | ||||
| 	@Event(ThemeChangeEvent.class) | ||||
| 	@Priority(150) | ||||
| 	private void onThemeChange() { | ||||
| 		applyCSS(); | ||||
| 	} | ||||
| @@ -182,7 +144,7 @@ public final class SceneContext implements EventListener { | ||||
| 	 * @return the controller used by the current scene | ||||
| 	 * @since Envoy Client v0.1-beta | ||||
| 	 */ | ||||
| 	public <T> T getController() { return (T) controllerStack.peek(); } | ||||
| 	public <T> T getController() { return (T) controllers.peek(); } | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the stage in which the scenes are displayed | ||||
| @@ -194,5 +156,5 @@ public final class SceneContext implements EventListener { | ||||
| 	 * @return whether the scene stack is empty | ||||
| 	 * @since Envoy Client v0.2-beta | ||||
| 	 */ | ||||
| 	public boolean isEmpty() { return sceneStack.isEmpty(); } | ||||
| 	public boolean isEmpty() { return roots.isEmpty(); } | ||||
| } | ||||
|   | ||||
							
								
								
									
										40
									
								
								client/src/main/java/envoy/client/ui/SceneInfo.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								client/src/main/java/envoy/client/ui/SceneInfo.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| package envoy.client.ui; | ||||
|  | ||||
| /** | ||||
|  * Contains information about different scenes and their FXML resource files. | ||||
|  * | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since Envoy Client v0.1-beta | ||||
|  */ | ||||
| public enum SceneInfo { | ||||
|  | ||||
| 	/** | ||||
| 	 * The main scene in which the chat screen is displayed. | ||||
| 	 * | ||||
| 	 * @since Envoy Client v0.1-beta | ||||
| 	 */ | ||||
| 	CHAT_SCENE("/fxml/ChatScene.fxml"), | ||||
|  | ||||
| 	/** | ||||
| 	 * The scene in which the settings screen is displayed. | ||||
| 	 * | ||||
| 	 * @since Envoy Client v0.1-beta | ||||
| 	 */ | ||||
| 	SETTINGS_SCENE("/fxml/SettingsScene.fxml"), | ||||
|  | ||||
| 	/** | ||||
| 	 * The scene in which the login screen is displayed. | ||||
| 	 * | ||||
| 	 * @since Envoy Client v0.1-beta | ||||
| 	 */ | ||||
| 	LOGIN_SCENE("/fxml/LoginScene.fxml"); | ||||
|  | ||||
| 	/** | ||||
| 	 * The path to the FXML resource. | ||||
| 	 */ | ||||
| 	public final String path; | ||||
|  | ||||
| 	SceneInfo(String path) { | ||||
| 		this.path = path; | ||||
| 	} | ||||
| } | ||||
| @@ -12,7 +12,7 @@ import javafx.stage.Stage; | ||||
|  | ||||
| import envoy.data.*; | ||||
| import envoy.data.User.UserStatus; | ||||
| import envoy.event.*; | ||||
| import envoy.event.UserStatusChange; | ||||
| import envoy.exception.EnvoyException; | ||||
| import envoy.util.EnvoyLog; | ||||
|  | ||||
| @@ -20,7 +20,6 @@ import envoy.client.data.*; | ||||
| import envoy.client.data.shortcuts.EnvoyShortcutConfig; | ||||
| import envoy.client.helper.ShutdownHelper; | ||||
| import envoy.client.net.Client; | ||||
| import envoy.client.ui.SceneContext.SceneInfo; | ||||
| import envoy.client.ui.controller.LoginScene; | ||||
| import envoy.client.util.IconUtil; | ||||
|  | ||||
| @@ -94,7 +93,7 @@ public final class Startup extends Application { | ||||
| 		final var sceneContext = new SceneContext(stage); | ||||
| 		context.setSceneContext(sceneContext); | ||||
|  | ||||
| 		// Authenticate with token if present | ||||
| 		// Authenticate with token if present or load login scene | ||||
| 		if (localDB.getAuthToken() != null) { | ||||
| 			logger.info("Attempting authentication with token..."); | ||||
| 			localDB.loadUserData(); | ||||
| @@ -103,8 +102,9 @@ public final class Startup extends Application { | ||||
| 					VERSION, localDB.getLastSync()))) | ||||
| 				sceneContext.load(SceneInfo.LOGIN_SCENE); | ||||
| 		} else | ||||
| 			// Load login scene | ||||
| 			sceneContext.load(SceneInfo.LOGIN_SCENE); | ||||
|  | ||||
| 		stage.show(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -115,21 +115,20 @@ public final class Startup extends Application { | ||||
| 	 * @since Envoy Client v0.2-beta | ||||
| 	 */ | ||||
| 	public static boolean performHandshake(LoginCredentials credentials) { | ||||
| 		final var cacheMap = new CacheMap(); | ||||
| 		cacheMap.put(Message.class, new Cache<Message>()); | ||||
| 		cacheMap.put(GroupMessage.class, new Cache<GroupMessage>()); | ||||
| 		cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>()); | ||||
| 		cacheMap.put(GroupMessageStatusChange.class, new Cache<GroupMessageStatusChange>()); | ||||
| 		final var originalStatus = | ||||
| 			localDB.getUser() == null ? UserStatus.ONLINE : localDB.getUser().getStatus(); | ||||
| 		try { | ||||
| 			client.performHandshake(credentials, cacheMap); | ||||
| 			client.performHandshake(credentials); | ||||
| 			if (client.isOnline()) { | ||||
|  | ||||
| 				// Restore the original status as the server automatically returns status ONLINE | ||||
| 				client.getSender().setStatus(originalStatus); | ||||
| 				loadChatScene(); | ||||
| 				client.initReceiver(localDB, cacheMap); | ||||
|  | ||||
| 				// Request an ID generator if none is present or the existing one is consumed | ||||
| 				if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext()) | ||||
| 					client.requestIDGenerator(); | ||||
|  | ||||
| 				return true; | ||||
| 			} else | ||||
| 				return false; | ||||
| @@ -226,7 +225,7 @@ public final class Startup extends Application { | ||||
| 		// Load ChatScene | ||||
| 		stage.setMinHeight(400); | ||||
| 		stage.setMinWidth(843); | ||||
| 		context.getSceneContext().load(SceneContext.SceneInfo.CHAT_SCENE); | ||||
| 		context.getSceneContext().load(SceneInfo.CHAT_SCENE); | ||||
| 		stage.centerOnScreen(); | ||||
|  | ||||
| 		// Exit or minimize the stage when a close request occurs | ||||
|   | ||||
| @@ -9,8 +9,8 @@ import java.awt.image.BufferedImage; | ||||
| import javafx.application.Platform; | ||||
| import javafx.stage.Stage; | ||||
|  | ||||
| import dev.kske.eventbus.*; | ||||
| import dev.kske.eventbus.Event; | ||||
| import dev.kske.eventbus.core.Event; | ||||
| import dev.kske.eventbus.core.EventBus; | ||||
|  | ||||
| import envoy.data.Message; | ||||
| import envoy.data.User.UserStatus; | ||||
| @@ -31,7 +31,7 @@ import envoy.client.util.*; | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since Envoy Client v0.2-alpha | ||||
|  */ | ||||
| public final class StatusTrayIcon implements EventListener { | ||||
| public final class StatusTrayIcon { | ||||
|  | ||||
| 	/** | ||||
| 	 * The {@link TrayIcon} provided by the System Tray API for controlling the system tray. This | ||||
| @@ -136,7 +136,7 @@ public final class StatusTrayIcon implements EventListener { | ||||
| 	 * | ||||
| 	 * @since Envoy Client v0.2-beta | ||||
| 	 */ | ||||
| 	@Event(eventType = Logout.class) | ||||
| 	@Event(Logout.class) | ||||
| 	public void hide() { | ||||
| 		SystemTray.getSystemTray().remove(trayIcon); | ||||
| 	} | ||||
|   | ||||
| @@ -15,7 +15,7 @@ import envoy.util.EnvoyLog; | ||||
| import envoy.client.data.Context; | ||||
| import envoy.client.data.commands.*; | ||||
| import envoy.client.helper.ShutdownHelper; | ||||
| import envoy.client.ui.SceneContext.SceneInfo; | ||||
| import envoy.client.ui.SceneInfo; | ||||
| import envoy.client.ui.controller.ChatScene; | ||||
| import envoy.client.util.*; | ||||
|  | ||||
| @@ -32,7 +32,7 @@ public final class ChatSceneCommands { | ||||
| 	private final SystemCommandBuilder	builder					= | ||||
| 		new SystemCommandBuilder(messageTextAreaCommands); | ||||
|  | ||||
| 	private static final String messageDependantCommandDescription = | ||||
| 	private static final String messageDependentCommandDescription = | ||||
| 		" the given message. Use s/S to use the selected message. Otherwise expects a number relative to the uppermost completely visible message."; | ||||
|  | ||||
| 	/** | ||||
| @@ -141,7 +141,7 @@ public final class ChatSceneCommands { | ||||
| 			else | ||||
| 				useRelativeMessage(command, action, additionalCheck, positionalArgument, false); | ||||
| 		}).setDefaults("s").setNumberOfArguments(1) | ||||
| 			.setDescription(description.concat(messageDependantCommandDescription)).build(command); | ||||
| 			.setDescription(description.concat(messageDependentCommandDescription)).build(command); | ||||
| 	} | ||||
|  | ||||
| 	private void selectionNeighbor(Consumer<Message> action, Predicate<Message> additionalCheck, | ||||
|   | ||||
| @@ -18,7 +18,7 @@ import envoy.client.util.IconUtil; | ||||
|  */ | ||||
| public final class ChatControl extends HBox { | ||||
|  | ||||
| 	private static final Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32), | ||||
| 	private static Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32), | ||||
| 		groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32); | ||||
|  | ||||
| 	/** | ||||
| @@ -60,4 +60,14 @@ public final class ChatControl extends HBox { | ||||
| 		} | ||||
| 		getStyleClass().add("list-element"); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Reloads the default icons. | ||||
| 	 * | ||||
| 	 * @since Envoy Client v0.3-beta | ||||
| 	 */ | ||||
| 	public static void reloadDefaultChatIcons() { | ||||
| 		userIcon	= IconUtil.loadIconThemeSensitive("user_icon", 32); | ||||
| 		groupIcon	= IconUtil.loadIconThemeSensitive("group_icon", 32); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -1,11 +1,14 @@ | ||||
| package envoy.client.ui.controller; | ||||
|  | ||||
| import static envoy.client.ui.SceneInfo.SETTINGS_SCENE; | ||||
|  | ||||
| import java.awt.Toolkit; | ||||
| import java.awt.datatransfer.StringSelection; | ||||
| import java.io.*; | ||||
| import java.nio.file.Files; | ||||
| import java.time.LocalDateTime; | ||||
| import java.time.format.DateTimeFormatter; | ||||
| import java.util.Map; | ||||
| import java.util.logging.*; | ||||
|  | ||||
| import javafx.animation.RotateTransition; | ||||
| @@ -18,14 +21,14 @@ import javafx.scene.control.*; | ||||
| import javafx.scene.control.Alert.AlertType; | ||||
| import javafx.scene.image.*; | ||||
| import javafx.scene.input.*; | ||||
| import javafx.scene.layout.*; | ||||
| import javafx.scene.layout.HBox; | ||||
| import javafx.scene.paint.Color; | ||||
| import javafx.scene.shape.Rectangle; | ||||
| import javafx.stage.FileChooser; | ||||
| import javafx.util.Duration; | ||||
|  | ||||
| import dev.kske.eventbus.*; | ||||
| import dev.kske.eventbus.Event; | ||||
| import dev.kske.eventbus.core.*; | ||||
| import dev.kske.eventbus.core.Event; | ||||
|  | ||||
| import envoy.data.*; | ||||
| import envoy.data.Attachment.AttachmentType; | ||||
| @@ -37,6 +40,7 @@ import envoy.util.EnvoyLog; | ||||
|  | ||||
| import envoy.client.data.*; | ||||
| import envoy.client.data.audio.AudioRecorder; | ||||
| import envoy.client.data.shortcuts.KeyboardMapping; | ||||
| import envoy.client.event.*; | ||||
| import envoy.client.net.*; | ||||
| import envoy.client.ui.*; | ||||
| @@ -51,7 +55,7 @@ import envoy.client.util.*; | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since Envoy Client v0.1-beta | ||||
|  */ | ||||
| public final class ChatScene implements EventListener, Restorable { | ||||
| public final class ChatScene implements Restorable, KeyboardMapping { | ||||
|  | ||||
| 	@FXML | ||||
| 	private ListView<Message> messageList; | ||||
| @@ -216,12 +220,13 @@ public final class ChatScene implements EventListener, Restorable { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	@Event(eventType = BackEvent.class) | ||||
| 	@Event(BackEvent.class) | ||||
| 	private void onBackEvent() { | ||||
| 		tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal()); | ||||
| 	} | ||||
|  | ||||
| 	@Event(includeSubtypes = true) | ||||
| 	@Event | ||||
| 	@Polymorphic | ||||
| 	private void onMessage(Message message) { | ||||
|  | ||||
| 		// The sender of the message is the recipient of the chat | ||||
| @@ -300,7 +305,7 @@ public final class ChatScene implements EventListener, Restorable { | ||||
| 		})); | ||||
| 	} | ||||
|  | ||||
| 	@Event(eventType = NoAttachments.class) | ||||
| 	@Event(NoAttachments.class) | ||||
| 	private void onNoAttachments() { | ||||
| 		Platform.runLater(() -> { | ||||
| 			attachmentButton.setDisable(true); | ||||
| @@ -313,13 +318,15 @@ public final class ChatScene implements EventListener, Restorable { | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	@Event(priority = 150) | ||||
| 	@Event | ||||
| 	@Priority(150) | ||||
| 	private void onGroupCreationResult(GroupCreationResult result) { | ||||
| 		Platform.runLater(() -> newGroupButton.setDisable(result.get() == null)); | ||||
| 	} | ||||
|  | ||||
| 	@Event(eventType = ThemeChangeEvent.class) | ||||
| 	@Event(ThemeChangeEvent.class) | ||||
| 	private void onThemeChange() { | ||||
| 		ChatControl.reloadDefaultChatIcons(); | ||||
| 		settingsButton.setGraphic( | ||||
| 			new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE))); | ||||
| 		voiceButton.setGraphic( | ||||
| @@ -341,11 +348,17 @@ public final class ChatScene implements EventListener, Restorable { | ||||
| 				recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43)); | ||||
| 	} | ||||
|  | ||||
| 	@Event(eventType = Logout.class, priority = 200) | ||||
| 	@Event(Logout.class) | ||||
| 	@Priority(200) | ||||
| 	private void onLogout() { | ||||
| 		eventBus.removeListener(this); | ||||
| 	} | ||||
|  | ||||
| 	@Event(AccountDeletion.class) | ||||
| 	private void onAccountDeletion() { | ||||
| 		Platform.runLater(chatList::refresh); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void onRestore() { | ||||
| 		updateRemainingCharsLabel(); | ||||
| @@ -438,7 +451,7 @@ public final class ChatScene implements EventListener, Restorable { | ||||
| 	 */ | ||||
| 	@FXML | ||||
| 	private void settingsButtonClicked() { | ||||
| 		sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE); | ||||
| 		sceneContext.load(SETTINGS_SCENE); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -586,7 +599,7 @@ public final class ChatScene implements EventListener, Restorable { | ||||
| 		// IsTyping#millisecondsActive | ||||
| 		if (client.isOnline() && currentChat.getLastWritingEvent() | ||||
| 			+ IsTyping.millisecondsActive <= System.currentTimeMillis()) { | ||||
| 			client.send(new IsTyping(getChatID(), currentChat.getRecipient().getID())); | ||||
| 			client.send(new IsTyping(currentChat.getRecipient().getID())); | ||||
| 			currentChat.lastWritingEventWasNow(); | ||||
| 		} | ||||
|  | ||||
| @@ -600,19 +613,6 @@ public final class ChatScene implements EventListener, Restorable { | ||||
| 			checkPostConditions(false); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * 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 | ||||
| 	 * @since Envoy Client v0.1-beta | ||||
| @@ -786,7 +786,8 @@ public final class ChatScene implements EventListener, Restorable { | ||||
| 		attachmentView.setVisible(visible); | ||||
| 	} | ||||
|  | ||||
| 	@Event(eventType = OwnStatusChange.class, priority = 50) | ||||
| 	@Event(OwnStatusChange.class) | ||||
| 	@Priority(50) | ||||
| 	private void generateOwnStatusControl() { | ||||
|  | ||||
| 		// Update the own user status if present | ||||
| @@ -797,7 +798,7 @@ public final class ChatScene implements EventListener, Restorable { | ||||
| 			// Else prepend it to the HBox children | ||||
| 			final var ownUserControl = new ContactControl(localDB.getUser()); | ||||
| 			ownUserControl.setAlignment(Pos.CENTER_LEFT); | ||||
| 			HBox.setHgrow(ownUserControl, Priority.NEVER); | ||||
| 			HBox.setHgrow(ownUserControl, javafx.scene.layout.Priority.NEVER); | ||||
| 			ownContactControl.getChildren().add(1, ownUserControl); | ||||
| 		} | ||||
| 	} | ||||
| @@ -884,4 +885,25 @@ public final class ChatScene implements EventListener, Restorable { | ||||
| 			: c -> c.getRecipient().getName().toLowerCase() | ||||
| 				.contains(contactSearch.getText().toLowerCase())); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Map<KeyCombination, Runnable> getKeyboardShortcuts() { | ||||
| 		return Map.<KeyCombination, Runnable>of( | ||||
|  | ||||
| 			// Delete text before the caret with "Control" + U | ||||
| 			new KeyCodeCombination(KeyCode.U, KeyCombination.CONTROL_DOWN), () -> { | ||||
| 				messageTextArea | ||||
| 					.setText( | ||||
| 						messageTextArea.getText().substring(messageTextArea.getCaretPosition())); | ||||
| 				checkPostConditions(false); | ||||
|  | ||||
| 				// Delete text after the caret with "Control" + K | ||||
| 			}, new KeyCodeCombination(KeyCode.K, KeyCombination.CONTROL_DOWN), () -> { | ||||
| 				messageTextArea | ||||
| 					.setText( | ||||
| 						messageTextArea.getText().substring(0, messageTextArea.getCaretPosition())); | ||||
| 				checkPostConditions(false); | ||||
| 				messageTextArea.positionCaret(messageTextArea.getText().length()); | ||||
| 			}); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import javafx.fxml.FXML; | ||||
| import javafx.scene.control.*; | ||||
| import javafx.scene.control.Alert.AlertType; | ||||
|  | ||||
| import dev.kske.eventbus.*; | ||||
| import dev.kske.eventbus.core.*; | ||||
|  | ||||
| import envoy.data.User; | ||||
| import envoy.event.ElementOperation; | ||||
| @@ -34,7 +34,7 @@ import envoy.client.ui.listcell.ListCellFactory; | ||||
|  * @author Maximilian Käfer | ||||
|  * @since Envoy Client v0.1-beta | ||||
|  */ | ||||
| public class ContactSearchTab implements EventListener { | ||||
| public class ContactSearchTab { | ||||
|  | ||||
| 	@FXML | ||||
| 	private TextArea searchBar; | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import javafx.scene.control.*; | ||||
| import javafx.scene.input.MouseEvent; | ||||
| import javafx.scene.layout.HBox; | ||||
|  | ||||
| import dev.kske.eventbus.*; | ||||
| import dev.kske.eventbus.core.*; | ||||
|  | ||||
| import envoy.data.*; | ||||
| import envoy.event.GroupCreation; | ||||
| @@ -18,7 +18,7 @@ import envoy.event.contact.UserOperation; | ||||
| import envoy.util.Bounds; | ||||
|  | ||||
| import envoy.client.data.*; | ||||
| import envoy.client.event.BackEvent; | ||||
| import envoy.client.event.*; | ||||
| import envoy.client.ui.control.*; | ||||
| import envoy.client.ui.listcell.ListCellFactory; | ||||
|  | ||||
| @@ -33,7 +33,7 @@ import envoy.client.ui.listcell.ListCellFactory; | ||||
|  * @author Maximilian Käfer | ||||
|  * @since Envoy Client v0.1-beta | ||||
|  */ | ||||
| public class GroupCreationTab implements EventListener { | ||||
| public class GroupCreationTab { | ||||
|  | ||||
| 	@FXML | ||||
| 	private Button createButton; | ||||
| @@ -252,4 +252,10 @@ public class GroupCreationTab implements EventListener { | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	@Event | ||||
| 	private void onAccountDeletion(AccountDeletion deletion) { | ||||
| 		final var deletedID = deletion.get(); | ||||
| 		Platform.runLater(() -> userList.getItems().removeIf(user -> (user.getID() == deletedID))); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import javafx.scene.control.*; | ||||
| import javafx.scene.control.Alert.AlertType; | ||||
| import javafx.scene.image.ImageView; | ||||
|  | ||||
| import dev.kske.eventbus.*; | ||||
| import dev.kske.eventbus.core.*; | ||||
|  | ||||
| import envoy.data.LoginCredentials; | ||||
| import envoy.event.HandshakeRejection; | ||||
| @@ -27,7 +27,7 @@ import envoy.client.util.IconUtil; | ||||
|  * @author Maximilian Käfer | ||||
|  * @since Envoy Client v0.1-beta | ||||
|  */ | ||||
| public final class LoginScene implements EventListener { | ||||
| public final class LoginScene { | ||||
|  | ||||
| 	@FXML | ||||
| 	private TextField userTextField; | ||||
|   | ||||
| @@ -2,7 +2,7 @@ package envoy.client.ui.settings; | ||||
|  | ||||
| import javafx.scene.control.*; | ||||
|  | ||||
| import dev.kske.eventbus.EventBus; | ||||
| import dev.kske.eventbus.core.EventBus; | ||||
|  | ||||
| import envoy.data.User.UserStatus; | ||||
|  | ||||
|   | ||||
| @@ -13,14 +13,15 @@ import javafx.scene.image.*; | ||||
| import javafx.scene.input.InputEvent; | ||||
| import javafx.scene.layout.HBox; | ||||
| import javafx.stage.FileChooser; | ||||
| import javafx.util.Duration; | ||||
|  | ||||
| import dev.kske.eventbus.EventBus; | ||||
| import dev.kske.eventbus.core.EventBus; | ||||
|  | ||||
| import envoy.event.*; | ||||
| import envoy.util.*; | ||||
|  | ||||
| import envoy.client.ui.control.ProfilePicImageView; | ||||
| import envoy.client.util.IconUtil; | ||||
| import envoy.client.util.*; | ||||
|  | ||||
| /** | ||||
|  * @author Leon Hofmeister | ||||
| @@ -38,6 +39,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane { | ||||
| 	private final PasswordField	newPasswordField		= new PasswordField(); | ||||
| 	private final PasswordField	repeatNewPasswordField	= new PasswordField(); | ||||
| 	private final Button		saveButton				= new Button("Save"); | ||||
| 	private final Button		deleteAccountButton		= new Button("Delete Account (Locally)"); | ||||
|  | ||||
| 	private static final EventBus	eventBus	= EventBus.getInstance(); | ||||
| 	private static final Logger		logger		= EnvoyLog.getLogger(UserSettingsPane.class); | ||||
| @@ -112,16 +114,19 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane { | ||||
|  | ||||
| 		final PasswordField[]					passwordFields	= | ||||
| 			{ currentPasswordField, newPasswordField, repeatNewPasswordField }; | ||||
| 		final EventHandler<? super InputEvent>	passwordEntered	= e -> { | ||||
| 																	newPassword		= | ||||
| 																		newPasswordField.getText(); | ||||
| 																	validPassword	= newPassword | ||||
| 																		.equals( | ||||
| 																			repeatNewPasswordField | ||||
| 																				.getText()) | ||||
| 																		&& !newPasswordField | ||||
| 																			.getText().isBlank(); | ||||
| 																}; | ||||
| 		final EventHandler<? super InputEvent>	passwordEntered	= | ||||
| 			e -> { | ||||
| 																		newPassword	= | ||||
| 																			newPasswordField | ||||
| 																				.getText(); | ||||
| 																		validPassword = | ||||
| 																			newPassword.equals( | ||||
| 																				repeatNewPasswordField | ||||
| 																					.getText()) | ||||
| 																				&& !newPasswordField | ||||
| 																					.getText() | ||||
| 																					.isBlank(); | ||||
| 																	}; | ||||
| 		newPasswordField.setOnInputMethodTextChanged(passwordEntered); | ||||
| 		newPasswordField.setOnKeyTyped(passwordEntered); | ||||
| 		repeatNewPasswordField.setOnInputMethodTextChanged(passwordEntered); | ||||
| @@ -137,9 +142,21 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane { | ||||
|  | ||||
| 		// Displaying the save button | ||||
| 		saveButton | ||||
| 			.setOnAction(e -> save(client.getSender().getID(), currentPasswordField.getText())); | ||||
| 			.setOnAction(e -> save(currentPasswordField.getText())); | ||||
| 		saveButton.setAlignment(Pos.BOTTOM_RIGHT); | ||||
| 		getChildren().add(saveButton); | ||||
|  | ||||
| 		// Displaying the delete account button | ||||
| 		deleteAccountButton.setAlignment(Pos.BASELINE_CENTER); | ||||
| 		deleteAccountButton.setOnAction(e -> UserUtil.deleteAccount()); | ||||
| 		deleteAccountButton.setText("Delete Account (locally)"); | ||||
| 		deleteAccountButton.setPrefHeight(25); | ||||
| 		deleteAccountButton.getStyleClass().clear(); | ||||
| 		deleteAccountButton.getStyleClass().add("danger-button"); | ||||
| 		final var tooltip = new Tooltip("Remote deletion is currently unsupported."); | ||||
| 		tooltip.setShowDelay(Duration.millis(100)); | ||||
| 		deleteAccountButton.setTooltip(tooltip); | ||||
| 		getChildren().add(deleteAccountButton); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -148,11 +165,11 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane { | ||||
| 	 * @param username the new username | ||||
| 	 * @since Envoy Client v0.2-beta | ||||
| 	 */ | ||||
| 	private void save(long userID, String oldPassword) { | ||||
| 	private void save(String oldPassword) { | ||||
|  | ||||
| 		// The profile pic was changed | ||||
| 		if (profilePicChanged) { | ||||
| 			final var profilePicChangeEvent = new ProfilePicChange(currentImageBytes, userID); | ||||
| 			final var profilePicChangeEvent = new ProfilePicChange(currentImageBytes); | ||||
| 			eventBus.dispatch(profilePicChangeEvent); | ||||
| 			client.send(profilePicChangeEvent); | ||||
| 			logger.log(Level.INFO, "The user just changed his profile pic."); | ||||
| @@ -161,7 +178,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane { | ||||
| 		// The username was changed | ||||
| 		final var validContactName = Bounds.isValidContactName(newUsername); | ||||
| 		if (usernameChanged && validContactName) { | ||||
| 			final var nameChangeEvent = new NameChange(userID, newUsername); | ||||
| 			final var nameChangeEvent = new NameChange(client.getSender().getID(), newUsername); | ||||
| 			eventBus.dispatch(nameChangeEvent); | ||||
| 			client.send(nameChangeEvent); | ||||
| 			logger.log(Level.INFO, "The user just changed his name to " + newUsername + "."); | ||||
| @@ -178,7 +195,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane { | ||||
|  | ||||
| 		// The password was changed | ||||
| 		if (validPassword) { | ||||
| 			client.send(new PasswordChangeRequest(newPassword, oldPassword, userID)); | ||||
| 			client.send(new PasswordChangeRequest(newPassword, oldPassword)); | ||||
| 			logger.log(Level.INFO, "The user just tried to change his password!"); | ||||
| 		} else if (!(validPassword || newPassword.isBlank())) { | ||||
| 			final var alert = new Alert(AlertType.ERROR); | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import java.util.logging.*; | ||||
|  | ||||
| import javafx.stage.FileChooser; | ||||
|  | ||||
| import dev.kske.eventbus.EventBus; | ||||
| import dev.kske.eventbus.core.EventBus; | ||||
|  | ||||
| import envoy.data.Message; | ||||
| import envoy.util.EnvoyLog; | ||||
|   | ||||
| @@ -2,10 +2,10 @@ package envoy.client.util; | ||||
|  | ||||
| import java.util.logging.*; | ||||
|  | ||||
| import javafx.scene.control.Alert; | ||||
| import javafx.scene.control.*; | ||||
| import javafx.scene.control.Alert.AlertType; | ||||
|  | ||||
| import dev.kske.eventbus.EventBus; | ||||
| import dev.kske.eventbus.core.EventBus; | ||||
|  | ||||
| import envoy.data.*; | ||||
| import envoy.data.User.UserStatus; | ||||
| @@ -16,7 +16,7 @@ import envoy.util.EnvoyLog; | ||||
| import envoy.client.data.Context; | ||||
| import envoy.client.event.*; | ||||
| import envoy.client.helper.*; | ||||
| import envoy.client.ui.SceneContext.SceneInfo; | ||||
| import envoy.client.ui.SceneInfo; | ||||
| import envoy.client.ui.controller.ChatScene; | ||||
|  | ||||
| /** | ||||
| @@ -121,4 +121,35 @@ public final class UserUtil { | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Deletes anything pointing to this user, independent of client or server. Will do nothing if | ||||
| 	 * the client is currently offline. | ||||
| 	 * | ||||
| 	 * @since Envoy Client v0.3-beta | ||||
| 	 */ | ||||
| 	public static void deleteAccount() { | ||||
|  | ||||
| 		// Show the first wall of defense, if not disabled by the user | ||||
| 		final var outerAlert = new Alert(AlertType.CONFIRMATION); | ||||
| 		outerAlert.setContentText( | ||||
| 			"Are you sure you want to delete your account entirely? This action can seriously not be undone."); | ||||
| 		outerAlert.setTitle("Delete Account?"); | ||||
| 		AlertHelper.confirmAction(outerAlert, () -> { | ||||
|  | ||||
| 			// Show the final wall of defense in every case | ||||
| 			final var lastAlert = new Alert(AlertType.WARNING, | ||||
| 				"Do you REALLY want to delete your account? Last Warning. Proceed?", | ||||
| 				ButtonType.CANCEL, ButtonType.OK); | ||||
| 			lastAlert.setTitle("Delete Account?"); | ||||
| 			lastAlert.showAndWait().filter(ButtonType.OK::equals).ifPresent(b2 -> { | ||||
|  | ||||
| 				// Delete the account | ||||
| 				// TODO: Notify server of account deletion | ||||
| 				context.getLocalDB().delete(); | ||||
| 				logger.log(Level.INFO, "The user just deleted his account. Goodbye."); | ||||
| 				ShutdownHelper.exit(true); | ||||
| 			}); | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -16,12 +16,13 @@ module envoy.client { | ||||
| 	requires javafx.fxml; | ||||
| 	requires javafx.base; | ||||
| 	requires javafx.graphics; | ||||
| 	requires dev.kske.eventbus.core; | ||||
|  | ||||
| 	opens envoy.client.ui to javafx.graphics, javafx.fxml, dev.kske.eventbus; | ||||
| 	opens envoy.client.ui.controller to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus; | ||||
| 	opens envoy.client.ui.chatscene to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus; | ||||
| 	opens envoy.client.ui to javafx.graphics, javafx.fxml, dev.kske.eventbus.core; | ||||
| 	opens envoy.client.ui.controller to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus.core; | ||||
| 	opens envoy.client.ui.chatscene to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus.core; | ||||
| 	opens envoy.client.ui.control to javafx.graphics, javafx.fxml; | ||||
| 	opens envoy.client.ui.settings to envoy.client.util; | ||||
| 	opens envoy.client.net to dev.kske.eventbus; | ||||
| 	opens envoy.client.data to dev.kske.eventbus; | ||||
| 	opens envoy.client.net to dev.kske.eventbus.core; | ||||
| 	opens envoy.client.data to dev.kske.eventbus.core; | ||||
| } | ||||
|   | ||||
| @@ -70,6 +70,17 @@ | ||||
| 	-fx-text-fill: gray; | ||||
| } | ||||
|  | ||||
| .danger-button { | ||||
| 	-fx-background-color: red; | ||||
| 	-fx-text-fill: white; | ||||
| 	-fx-background-radius: 0.2em; | ||||
| } | ||||
|  | ||||
| .danger-button:hover { | ||||
| 	-fx-scale-x: 1.05; | ||||
| 	-fx-scale-y: 1.05; | ||||
| } | ||||
|  | ||||
| .received-message { | ||||
| 	-fx-alignment: center-left; | ||||
| 	-fx-background-radius: 1.3em; | ||||
|   | ||||
| @@ -30,6 +30,10 @@ | ||||
| 	-fx-background-color: black; | ||||
| } | ||||
|  | ||||
| .tooltip { | ||||
| 	-fx-text-fill: black; | ||||
| } | ||||
|  | ||||
| #login-input-field { | ||||
| 	-fx-border-color: black; | ||||
| } | ||||
|   | ||||
| @@ -24,8 +24,8 @@ | ||||
|  | ||||
| <GridPane maxHeight="-Infinity" maxWidth="-Infinity" | ||||
| 	minHeight="400.0" minWidth="500.0" | ||||
| 	prefHeight="${screen.visualBounds.height}" | ||||
| 	prefWidth="${screen.visualBounds.width}" | ||||
| 	prefHeight="${screen.visualBounds.height}" | ||||
| 	xmlns="http://javafx.com/javafx/11.0.1" | ||||
| 	xmlns:fx="http://javafx.com/fxml/1" | ||||
| 	fx:controller="envoy.client.ui.controller.ChatScene"> | ||||
| @@ -57,8 +57,7 @@ | ||||
| 					<content> | ||||
| 						<AnchorPane minHeight="0.0" minWidth="0.0"> | ||||
| 							<children> | ||||
| 								<VBox prefHeight="3000.0" | ||||
| 									prefWidth="316.0"> | ||||
| 								<VBox prefHeight="3000.0" prefWidth="316.0"> | ||||
| 									<children> | ||||
| 										<VBox id="search-panel" maxHeight="-Infinity" | ||||
| 											minHeight="-Infinity" prefHeight="80.0" prefWidth="316.0"> | ||||
| @@ -156,8 +155,7 @@ | ||||
| 						<Insets left="15.0" top="5.0" right="10.0" /> | ||||
| 					</HBox.margin> | ||||
| 				</ImageView> | ||||
| 				<Region id="transparent-background" | ||||
| 					HBox.hgrow="ALWAYS" /> | ||||
| 				<Region id="transparent-background" HBox.hgrow="ALWAYS" /> | ||||
| 				<Button fx:id="settingsButton" mnemonicParsing="false" | ||||
| 					onAction="#settingsButtonClicked" prefHeight="30.0" | ||||
| 					prefWidth="30.0" alignment="CENTER_RIGHT"> | ||||
| @@ -165,7 +163,7 @@ | ||||
| 						<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> | ||||
| 					</padding> | ||||
| 					<HBox.margin> | ||||
| 						<Insets bottom="35.0" left="5.0" top="35.0" right="10.0"/> | ||||
| 						<Insets bottom="35.0" left="5.0" top="35.0" right="10.0" /> | ||||
| 					</HBox.margin> | ||||
| 				</Button> | ||||
| 			</children> | ||||
|   | ||||
| @@ -7,11 +7,16 @@ | ||||
| <?import javafx.scene.layout.HBox?> | ||||
| <?import javafx.scene.layout.VBox?> | ||||
|  | ||||
| <VBox alignment="TOP_RIGHT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.SettingsScene"> | ||||
| <VBox alignment="TOP_RIGHT" maxHeight="-Infinity" minHeight="400.0" | ||||
| 	minWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" | ||||
| 	xmlns:fx="http://javafx.com/fxml/1" | ||||
| 	fx:controller="envoy.client.ui.controller.SettingsScene"> | ||||
| 	<children> | ||||
| 		<HBox prefHeight="389.0" prefWidth="600.0"> | ||||
| 			<children> | ||||
| 				<ListView id="message-list" fx:id="settingsList" onMouseClicked="#settingsListClicked" prefHeight="200.0" prefWidth="200.0"> | ||||
| 				<ListView id="message-list" fx:id="settingsList" | ||||
| 					onMouseClicked="#settingsListClicked" prefHeight="200.0" | ||||
| 					prefWidth="200.0"> | ||||
| 					<opaqueInsets> | ||||
| 						<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> | ||||
| 					</opaqueInsets> | ||||
| @@ -22,7 +27,8 @@ | ||||
| 						<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" /> | ||||
| 					</padding> | ||||
| 				</ListView> | ||||
| 				<TitledPane fx:id="titledPane" collapsible="false" prefHeight="400.0" prefWidth="400.0"> | ||||
| 				<TitledPane fx:id="titledPane" collapsible="false" | ||||
| 					prefHeight="400.0" prefWidth="400.0"> | ||||
| 					<HBox.margin> | ||||
| 						<Insets bottom="10.0" left="5.0" right="10.0" top="10.0" /> | ||||
| 					</HBox.margin> | ||||
| @@ -32,7 +38,8 @@ | ||||
| 				</TitledPane> | ||||
| 			</children> | ||||
| 		</HBox> | ||||
| 		<Button defaultButton="true" mnemonicParsing="true" onMouseClicked="#backButtonClicked" text="_Back"> | ||||
| 		<Button defaultButton="true" mnemonicParsing="true" | ||||
| 			onMouseClicked="#backButtonClicked" text="_Back"> | ||||
| 			<opaqueInsets> | ||||
| 				<Insets /> | ||||
| 			</opaqueInsets> | ||||
|   | ||||
| @@ -12,18 +12,11 @@ | ||||
| 		<version>0.2-beta</version> | ||||
| 	</parent> | ||||
|  | ||||
| 	<repositories> | ||||
| 		<repository> | ||||
| 			<id>kske-repo</id> | ||||
| 			<url>https://kske.dev/maven-repo</url> | ||||
| 		</repository> | ||||
| 	</repositories> | ||||
|  | ||||
| 	<dependencies> | ||||
| 		<dependency> | ||||
| 			<groupId>dev.kske</groupId> | ||||
| 			<artifactId>event-bus</artifactId> | ||||
| 			<version>0.0.4</version> | ||||
| 			<artifactId>event-bus-core</artifactId> | ||||
| 			<version>1.0.0</version> | ||||
| 		</dependency> | ||||
| 		<dependency> | ||||
| 			<groupId>org.junit.jupiter</groupId> | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package envoy.data; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * This interface should be used for any type supposed to be a {@link Message} attachment (i.e. | ||||
| @@ -63,9 +64,9 @@ public final class Attachment implements Serializable { | ||||
| 	 * @since Envoy Common v0.1-beta | ||||
| 	 */ | ||||
| 	public Attachment(byte[] data, String name, AttachmentType type) { | ||||
| 		this.data	= data; | ||||
| 		this.name	= name; | ||||
| 		this.type	= type; | ||||
| 		this.data	= Objects.requireNonNull(data); | ||||
| 		this.name	= Objects.requireNonNull(name); | ||||
| 		this.type	= Objects.requireNonNull(type); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -29,8 +29,8 @@ public abstract class Contact implements Serializable { | ||||
| 	 */ | ||||
| 	public Contact(long id, String name, Set<? extends Contact> contacts) { | ||||
| 		this.id			= id; | ||||
| 		this.name		= name; | ||||
| 		this.contacts	= contacts; | ||||
| 		this.name		= Objects.requireNonNull(name); | ||||
| 		this.contacts	= contacts == null ? new HashSet<>() : contacts; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -38,7 +38,8 @@ public final class GroupMessage extends Message { | ||||
| 		Map<Long, MessageStatus> memberStatuses) { | ||||
| 		super(id, senderID, groupID, creationDate, receivedDate, readDate, text, attachment, status, | ||||
| 			forwarded); | ||||
| 		this.memberStatuses = memberStatuses; | ||||
| 		this.memberStatuses = | ||||
| 			memberStatuses == null ? new HashMap<>() : memberStatuses; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -2,15 +2,13 @@ package envoy.data; | ||||
|  | ||||
| import java.io.Serializable; | ||||
|  | ||||
| import dev.kske.eventbus.IEvent; | ||||
|  | ||||
| /** | ||||
|  * Generates increasing IDs between two numbers. | ||||
|  * | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since Envoy Common v0.2-alpha | ||||
|  */ | ||||
| public final class IDGenerator implements IEvent, Serializable { | ||||
| public final class IDGenerator implements Serializable { | ||||
|  | ||||
| 	private final long	end; | ||||
| 	private long		current; | ||||
|   | ||||
| @@ -2,6 +2,7 @@ package envoy.data; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.Instant; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * Contains a {@link User}'s login / registration information as well as the client version. | ||||
| @@ -20,15 +21,14 @@ public final class LoginCredentials implements Serializable { | ||||
| 	private static final long serialVersionUID = 4; | ||||
|  | ||||
| 	private LoginCredentials(String identifier, String password, boolean registration, | ||||
| 		boolean token, boolean requestToken, String clientVersion, | ||||
| 		Instant lastSync) { | ||||
| 		this.identifier		= identifier; | ||||
| 		this.password		= password; | ||||
| 		boolean token, boolean requestToken, String clientVersion, Instant lastSync) { | ||||
| 		this.identifier		= Objects.requireNonNull(identifier); | ||||
| 		this.password		= Objects.requireNonNull(password); | ||||
| 		this.registration	= registration; | ||||
| 		this.token			= token; | ||||
| 		this.requestToken	= requestToken; | ||||
| 		this.clientVersion	= clientVersion; | ||||
| 		this.lastSync		= lastSync; | ||||
| 		this.clientVersion	= Objects.requireNonNull(clientVersion); | ||||
| 		this.lastSync		= lastSync == null ? Instant.EPOCH : lastSync; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -75,7 +75,8 @@ public final class LoginCredentials implements Serializable { | ||||
| 	 * @since Envoy Common v0.2-beta | ||||
| 	 */ | ||||
| 	public static LoginCredentials registration(String identifier, String password, | ||||
| 		boolean requestToken, String clientVersion, Instant lastSync) { | ||||
| 		boolean requestToken, | ||||
| 		String clientVersion, Instant lastSync) { | ||||
| 		return new LoginCredentials(identifier, password, true, false, requestToken, clientVersion, | ||||
| 			lastSync); | ||||
| 	} | ||||
|   | ||||
| @@ -2,8 +2,7 @@ package envoy.data; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.Instant; | ||||
|  | ||||
| import dev.kske.eventbus.IEvent; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * Represents a unique message with a unique, numeric ID. Further metadata includes the sender and | ||||
| @@ -13,7 +12,7 @@ import dev.kske.eventbus.IEvent; | ||||
|  * @author Leon Hofmeister | ||||
|  * @since Envoy Common v0.2-alpha | ||||
|  */ | ||||
| public class Message implements Serializable, IEvent { | ||||
| public class Message implements Serializable { | ||||
|  | ||||
| 	/** | ||||
| 	 * This enumeration defines all possible statuses a {link Message} can have. | ||||
| @@ -80,9 +79,9 @@ public class Message implements Serializable, IEvent { | ||||
| 		this.creationDate	= creationDate; | ||||
| 		this.receivedDate	= receivedDate; | ||||
| 		this.readDate		= readDate; | ||||
| 		this.text			= text; | ||||
| 		this.text			= text == null ? "" : text; | ||||
| 		this.attachment		= attachment; | ||||
| 		this.status			= status; | ||||
| 		this.status			= Objects.requireNonNull(status); | ||||
| 		this.forwarded		= forwarded; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -86,7 +86,7 @@ public final class User extends Contact { | ||||
| 	 */ | ||||
| 	public User(long id, String name, UserStatus status, Set<Contact> contacts) { | ||||
| 		super(id, name, contacts); | ||||
| 		this.status = status; | ||||
| 		this.status = Objects.requireNonNull(status); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
|   | ||||
| @@ -1,26 +1,33 @@ | ||||
| package envoy.event; | ||||
|  | ||||
| import java.io.Serializable; | ||||
|  | ||||
| import dev.kske.eventbus.IEvent; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * This class serves as a convenience base class for all events. It implements the {@link IEvent} | ||||
|  * interface and provides a generic value. For events without a value there also is | ||||
|  * {@link envoy.event.Event.Valueless}. | ||||
|  * This class serves as a convenience base class for all events. It provides a generic value. For | ||||
|  * events without a value there also is {@link envoy.event.Event.Valueless}. | ||||
|  * | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @param <T> the type of the Event | ||||
|  * @since Envoy v0.2-alpha | ||||
|  */ | ||||
| public abstract class Event<T> implements IEvent, Serializable { | ||||
| public abstract class Event<T> implements Serializable { | ||||
|  | ||||
| 	protected final T value; | ||||
|  | ||||
| 	private static final long serialVersionUID = 0L; | ||||
|  | ||||
| 	protected Event(T value) { | ||||
| 		this.value = value; | ||||
| 		this(value, false); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * This constructor is reserved for {@link Valueless} events. No other event should contain null | ||||
| 	 * values. Only use if really necessary. Using this constructor with {@code true} implies that | ||||
| 	 * the user has to manually check if the value of the event is null. | ||||
| 	 */ | ||||
| 	protected Event(T value, boolean canBeNull) { | ||||
| 		this.value = canBeNull ? value : Objects.requireNonNull(value); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -46,7 +53,7 @@ public abstract class Event<T> implements IEvent, Serializable { | ||||
| 		private static final long serialVersionUID = 0L; | ||||
|  | ||||
| 		protected Valueless() { | ||||
| 			super(null); | ||||
| 			super(null, true); | ||||
| 		} | ||||
|  | ||||
| 		@Override | ||||
|   | ||||
| @@ -20,7 +20,7 @@ public class GroupCreationResult extends Event<Group> { | ||||
| 	 * @since Envoy Common v0.2-beta | ||||
| 	 */ | ||||
| 	public GroupCreationResult() { | ||||
| 		super(null); | ||||
| 		super(null, true); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -30,7 +30,7 @@ public final class GroupMessageStatusChange extends MessageStatusChange { | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the memberID which the user who sends this event has | ||||
| 	 * @return the ID of the sender of this event | ||||
| 	 * @since Envoy Common v0.1-beta | ||||
| 	 */ | ||||
| 	public long getMemberID() { return memberID; } | ||||
|   | ||||
| @@ -2,6 +2,8 @@ package envoy.event; | ||||
|  | ||||
| import static envoy.event.ElementOperation.*; | ||||
|  | ||||
| import java.util.Objects; | ||||
|  | ||||
| import envoy.data.*; | ||||
|  | ||||
| /** | ||||
| @@ -30,7 +32,7 @@ public final class GroupResize extends Event<User> { | ||||
| 	 */ | ||||
| 	public GroupResize(User user, Group group, ElementOperation operation) { | ||||
| 		super(user); | ||||
| 		this.operation = operation; | ||||
| 		this.operation = Objects.requireNonNull(operation); | ||||
| 		final var contained = group.getContacts().contains(user); | ||||
| 		if (contained && operation.equals(ADD)) | ||||
| 			throw new IllegalArgumentException(String.format("Cannot add %s to %s!", user, group)); | ||||
|   | ||||
| @@ -8,8 +8,6 @@ package envoy.event; | ||||
|  */ | ||||
| public final class IsTyping extends Event<Long> { | ||||
|  | ||||
| 	private final long destinationID; | ||||
|  | ||||
| 	private static final long serialVersionUID = 1L; | ||||
|  | ||||
| 	/** | ||||
| @@ -22,20 +20,13 @@ public final class IsTyping extends Event<Long> { | ||||
| 	public static final int millisecondsActive = 3500; | ||||
|  | ||||
| 	/** | ||||
| 	 * Creates a new {@code IsTyping} event with originator and recipient. | ||||
| 	 * Creates a new {@code IsTyping}. The client will only send the contact that should receive | ||||
| 	 * this event. The server will send the id of the contact who sent this event. | ||||
| 	 * | ||||
| 	 * @param sourceID      the ID of the originator | ||||
| 	 * @param destinationID the ID of the contact the user wrote to | ||||
| 	 * @param id the ID of the recipient (client)/ originator(server) | ||||
| 	 * @since Envoy Common v0.2-beta | ||||
| 	 */ | ||||
| 	public IsTyping(Long sourceID, long destinationID) { | ||||
| 		super(sourceID); | ||||
| 		this.destinationID = destinationID; | ||||
| 	public IsTyping(long id) { | ||||
| 		super(id); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the ID of the contact in whose chat the user typed something | ||||
| 	 * @since Envoy Common v0.2-beta | ||||
| 	 */ | ||||
| 	public long getDestinationID() { return destinationID; } | ||||
| } | ||||
|   | ||||
| @@ -23,7 +23,7 @@ public final class IssueProposal extends Event<String> { | ||||
| 	 */ | ||||
| 	public IssueProposal(String title, String description, boolean isBug) { | ||||
| 		super(escape(title)); | ||||
| 		this.description	= sanitizeDescription(description); | ||||
| 		this.description	= description == null ? "" : sanitizeDescription(description); | ||||
| 		bug					= isBug; | ||||
| 	} | ||||
|  | ||||
| @@ -37,8 +37,8 @@ public final class IssueProposal extends Event<String> { | ||||
| 	 */ | ||||
| 	public IssueProposal(String title, String description, String user, boolean isBug) { | ||||
| 		super(escape(title)); | ||||
| 		this.description	= | ||||
| 			sanitizeDescription(description) + String.format("<br>Submitted by user %s.", user); | ||||
| 		this.description	= description == null ? "" | ||||
| 			: sanitizeDescription(description) + String.format("<br>Submitted by user %s.", user); | ||||
| 		bug					= isBug; | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package envoy.event; | ||||
|  | ||||
| import java.time.Instant; | ||||
| import java.util.Objects; | ||||
|  | ||||
| import envoy.data.Message; | ||||
|  | ||||
| @@ -26,7 +27,7 @@ public class MessageStatusChange extends Event<Message.MessageStatus> { | ||||
| 	public MessageStatusChange(long id, Message.MessageStatus status, Instant date) { | ||||
| 		super(status); | ||||
| 		this.id		= id; | ||||
| 		this.date	= date; | ||||
| 		this.date	= Objects.requireNonNull(date); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| package envoy.event; | ||||
|  | ||||
| import envoy.data.Contact; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * @author Leon Hofmeister | ||||
| @@ -8,29 +8,20 @@ import envoy.data.Contact; | ||||
|  */ | ||||
| public final class PasswordChangeRequest extends Event<String> { | ||||
|  | ||||
| 	private final long		id; | ||||
| 	private final String	oldPassword; | ||||
| 	private final String oldPassword; | ||||
|  | ||||
| 	private static final long serialVersionUID = 0L; | ||||
|  | ||||
| 	/** | ||||
| 	 * @param newPassword the new password of that user | ||||
| 	 * @param oldPassword the old password of that user | ||||
| 	 * @param userID      the ID of the user who wants to change his password | ||||
| 	 * @since Envoy Common v0.2-beta | ||||
| 	 */ | ||||
| 	public PasswordChangeRequest(String newPassword, String oldPassword, long userID) { | ||||
| 	public PasswordChangeRequest(String newPassword, String oldPassword) { | ||||
| 		super(newPassword); | ||||
| 		this.oldPassword	= oldPassword; | ||||
| 		id					= userID; | ||||
| 		this.oldPassword = Objects.requireNonNull(oldPassword); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the ID of the {@link Contact} this event is related to | ||||
| 	 * @since Envoy Common v0.2-alpha | ||||
| 	 */ | ||||
| 	public long getID() { return id; } | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the old password of the underlying user | ||||
| 	 * @since Envoy Common v0.2-beta | ||||
| @@ -39,6 +30,6 @@ public final class PasswordChangeRequest extends Event<String> { | ||||
|  | ||||
| 	@Override | ||||
| 	public String toString() { | ||||
| 		return "PasswordChangeRequest[id=" + id + "]"; | ||||
| 		return "PasswordChangeRequest[]"; | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -6,23 +6,13 @@ package envoy.event; | ||||
|  */ | ||||
| public final class ProfilePicChange extends Event<byte[]> { | ||||
|  | ||||
| 	private final long id; | ||||
|  | ||||
| 	private static final long serialVersionUID = 0L; | ||||
|  | ||||
| 	/** | ||||
| 	 * @param value  the byte[] of the new image | ||||
| 	 * @param userID the ID of the user who changed his profile pic | ||||
| 	 * @param value the byte[] of the new image | ||||
| 	 * @since Envoy Common v0.2-beta | ||||
| 	 */ | ||||
| 	public ProfilePicChange(byte[] value, long userID) { | ||||
| 	public ProfilePicChange(byte[] value) { | ||||
| 		super(value); | ||||
| 		id = userID; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the ID of the user changing his profile pic | ||||
| 	 * @since Envoy Common v0.2-beta | ||||
| 	 */ | ||||
| 	public long getId() { return id; } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package envoy.event.contact; | ||||
|  | ||||
| import java.util.Objects; | ||||
|  | ||||
| import envoy.data.User; | ||||
| import envoy.event.*; | ||||
|  | ||||
| @@ -24,7 +26,7 @@ public final class UserOperation extends Event<User> { | ||||
| 	 */ | ||||
| 	public UserOperation(User contact, ElementOperation operationType) { | ||||
| 		super(contact); | ||||
| 		this.operationType = operationType; | ||||
| 		this.operationType = Objects.requireNonNull(operationType); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -16,5 +16,5 @@ module envoy.common { | ||||
| 	exports envoy.event.contact; | ||||
|  | ||||
| 	requires transitive java.logging; | ||||
| 	requires transitive dev.kske.eventbus; | ||||
| 	requires transitive dev.kske.eventbus.core; | ||||
| } | ||||
|   | ||||
| @@ -18,10 +18,10 @@ import envoy.util.SerializationUtils; | ||||
|  * @author Leon Hofmeister | ||||
|  * @since Envoy Common v0.1-beta | ||||
|  */ | ||||
| class UserTest { | ||||
| public class UserTest { | ||||
|  | ||||
| 	@Test | ||||
| 	void test() throws IOException, ClassNotFoundException { | ||||
| 	public void test() throws IOException, ClassNotFoundException { | ||||
| 		User	user2				= new User(2, "kai"); | ||||
| 		User	user3				= new User(3, "ai"); | ||||
| 		User	user4				= new User(4, "ki", Set.of(user2, user3)); | ||||
|   | ||||
| @@ -1,285 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <diagram program="umlet" version="14.3.0"> | ||||
|   <zoom_level>10</zoom_level> | ||||
|   <element> | ||||
|     <id>UMLClass</id> | ||||
|     <coordinates> | ||||
|       <x>50</x> | ||||
|       <y>60</y> | ||||
|       <w>210</w> | ||||
|       <h>240</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>DBUser | ||||
| -- | ||||
| id: Long | ||||
| nick: String | ||||
| name: String | ||||
| chats: Set<DBChat> | ||||
| status: UserStatus | ||||
| created: Instant | ||||
| lastSeen: Instant | ||||
| deleted: Boolean | ||||
| avatar: DBAvatar | ||||
| passwordHash: String | ||||
| authToken: String | ||||
| authTokenExpiration: Instant | ||||
| -- | ||||
| +toCommon(): User</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLClass</id> | ||||
|     <coordinates> | ||||
|       <x>380</x> | ||||
|       <y>330</y> | ||||
|       <w>290</w> | ||||
|       <h>160</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>DBMessage | ||||
| -- | ||||
| id: Long | ||||
| sender: DBUser | ||||
| forwarded: Boolean | ||||
| deleted: Boolean | ||||
| statuses: Map<DBUser, DBMessageStatus> | ||||
| text: String | ||||
| attachment: DBAttachment | ||||
| -- | ||||
| +toCommon(): Message</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLClass</id> | ||||
|     <coordinates> | ||||
|       <x>770</x> | ||||
|       <y>320</y> | ||||
|       <w>210</w> | ||||
|       <h>100</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>DBMessageStatus | ||||
| -- | ||||
| id: Long | ||||
| status: MessageStatus | ||||
| timestamp: Instant | ||||
| -- | ||||
| +toCommon(): MessageStatus</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLClass</id> | ||||
|     <coordinates> | ||||
|       <x>770</x> | ||||
|       <y>440</y> | ||||
|       <w>180</w> | ||||
|       <h>120</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>DBAttachment | ||||
| -- | ||||
| id: Long | ||||
| name: String | ||||
| type: AttachmentType | ||||
| url: String | ||||
| -- | ||||
| +toCommon(): Attachment</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLClass</id> | ||||
|     <coordinates> | ||||
|       <x>510</x> | ||||
|       <y>70</y> | ||||
|       <w>200</w> | ||||
|       <h>180</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>DBChat | ||||
| -- | ||||
| id: Long | ||||
| name: String | ||||
| members: Set<DBUser> | ||||
| admins: Set<DBUser> | ||||
| messages: Set<DBMessage> | ||||
| avatar: DBAvatar | ||||
| allowJoining: Boolean | ||||
| allowAttachments: Boolean | ||||
| -- | ||||
| +toCommon(): Chat</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>250</x> | ||||
|       <y>100</y> | ||||
|       <w>280</w> | ||||
|       <h>40</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<-> | ||||
| m1=0..n | ||||
| m2=0..n</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;260.0;10.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>600</x> | ||||
|       <y>240</y> | ||||
|       <w>50</w> | ||||
|       <h>110</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=-> | ||||
| m2=0..n</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;10.0;90.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>660</x> | ||||
|       <y>360</y> | ||||
|       <w>130</w> | ||||
|       <h>40</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=-> | ||||
| m2=0..n</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;110.0;10.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>660</x> | ||||
|       <y>470</y> | ||||
|       <w>130</w> | ||||
|       <h>40</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=-> | ||||
| m2=0..1</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;110.0;10.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>140</x> | ||||
|       <y>290</y> | ||||
|       <w>260</w> | ||||
|       <h>150</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<- | ||||
| m1=0..n</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;10.0;130.0;240.0;130.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLPackage</id> | ||||
|     <coordinates> | ||||
|       <x>10</x> | ||||
|       <y>0</y> | ||||
|       <w>1010</w> | ||||
|       <h>590</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>envoy.server.data | ||||
| -- | ||||
| bg=#CCCCCC</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLClass</id> | ||||
|     <coordinates> | ||||
|       <x>90</x> | ||||
|       <y>680</y> | ||||
|       <w>170</w> | ||||
|       <h>130</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>User | ||||
| -- | ||||
| id: long | ||||
| nick: String | ||||
| name: String | ||||
| status: UserStatus | ||||
| created: Instant | ||||
| deleted: boolean</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLClass</id> | ||||
|     <coordinates> | ||||
|       <x>340</x> | ||||
|       <y>680</y> | ||||
|       <w>190</w> | ||||
|       <h>100</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>UserStatus | ||||
| -- | ||||
| _+ONLINE: UserStatus_ | ||||
| _+AWAY: UserStatus_ | ||||
| _+BUSY: UserStatus_ | ||||
| _+OFFLINE: UserStatus_</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>250</x> | ||||
|       <y>730</y> | ||||
|       <w>110</w> | ||||
|       <h>30</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<-</panel_attributes> | ||||
|     <additional_attributes>90.0;10.0;10.0;10.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLClass</id> | ||||
|     <coordinates> | ||||
|       <x>340</x> | ||||
|       <y>150</y> | ||||
|       <w>100</w> | ||||
|       <h>80</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>DBAvatar | ||||
| -- | ||||
| id: Long | ||||
| edited: Instant | ||||
| url: String | ||||
| layer=1</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>250</x> | ||||
|       <y>190</y> | ||||
|       <w>110</w> | ||||
|       <h>40</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<- | ||||
| m1=0..1</panel_attributes> | ||||
|     <additional_attributes>90.0;10.0;10.0;10.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>430</x> | ||||
|       <y>190</y> | ||||
|       <w>100</w> | ||||
|       <h>40</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<- | ||||
| m1=0..1</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;80.0;10.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLClass</id> | ||||
|     <coordinates> | ||||
|       <x>610</x> | ||||
|       <y>680</y> | ||||
|       <w>180</w> | ||||
|       <h>130</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>Chat | ||||
| -- | ||||
| id: long | ||||
| name: String | ||||
| deceased: boolean | ||||
| members: Set<User> | ||||
| admins: Set<User> | ||||
| messages: List<Message></panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
| </diagram> | ||||
| @@ -1,501 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <diagram program="umlet" version="14.3.0"> | ||||
|   <zoom_level>9</zoom_level> | ||||
|   <element> | ||||
|     <id>UMLActor</id> | ||||
|     <coordinates> | ||||
|       <x>72</x> | ||||
|       <y>540</y> | ||||
|       <w>54</w> | ||||
|       <h>99</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>Client1</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>2196</x> | ||||
|       <y>504</y> | ||||
|       <w>198</w> | ||||
|       <h>171</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>Database | ||||
| halign=center | ||||
| bg=orange | ||||
| --</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>351</x> | ||||
|       <y>513</y> | ||||
|       <w>90</w> | ||||
|       <h>117</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>Main</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>531</x> | ||||
|       <y>513</y> | ||||
|       <w>90</w> | ||||
|       <h>117</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>Startup | ||||
| bg=orange</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>702</x> | ||||
|       <y>513</y> | ||||
|       <w>90</w> | ||||
|       <h>117</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>Client | ||||
| bg=yellow</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>873</x> | ||||
|       <y>513</y> | ||||
|       <w>90</w> | ||||
|       <h>117</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>LocalDB | ||||
| bg=pink</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>1548</x> | ||||
|       <y>522</y> | ||||
|       <w>189</w> | ||||
|       <h>117</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>LoginCredentialsProcessor | ||||
| bg=cyan</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>1809</x> | ||||
|       <y>522</y> | ||||
|       <w>135</w> | ||||
|       <h>117</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>PersistenceManager | ||||
| bg=gray</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>396</x> | ||||
|       <y>729</y> | ||||
|       <w>189</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<<- | ||||
| start javaFX app</panel_attributes> | ||||
|     <additional_attributes>190.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>387</x> | ||||
|       <y>720</y> | ||||
|       <w>18</w> | ||||
|       <h>54</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes/> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>387</x> | ||||
|       <y>621</y> | ||||
|       <w>27</w> | ||||
|       <h>117</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=.</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;10.0;110.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>567</x> | ||||
|       <y>621</y> | ||||
|       <w>27</w> | ||||
|       <h>117</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=.</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;10.0;110.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>738</x> | ||||
|       <y>621</y> | ||||
|       <w>27</w> | ||||
|       <h>216</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=.</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;10.0;220.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>909</x> | ||||
|       <y>621</y> | ||||
|       <w>27</w> | ||||
|       <h>153</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=.</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;10.0;150.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>567</x> | ||||
|       <y>720</y> | ||||
|       <w>18</w> | ||||
|       <h>396</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>bg=orange</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>738</x> | ||||
|       <y>819</y> | ||||
|       <w>18</w> | ||||
|       <h>558</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>bg=yellow</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>909</x> | ||||
|       <y>756</y> | ||||
|       <w>18</w> | ||||
|       <h>621</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>bg=pink</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>576</x> | ||||
|       <y>756</y> | ||||
|       <w>351</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<<- | ||||
| initialize</panel_attributes> | ||||
|     <additional_attributes>370.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>1062</x> | ||||
|       <y>513</y> | ||||
|       <w>90</w> | ||||
|       <h>117</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>Receiver | ||||
| bg=red</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>1098</x> | ||||
|       <y>621</y> | ||||
|       <w>27</w> | ||||
|       <h>270</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=.</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;10.0;280.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>1098</x> | ||||
|       <y>873</y> | ||||
|       <w>18</w> | ||||
|       <h>504</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>bg=red</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>576</x> | ||||
|       <y>1071</y> | ||||
|       <w>180</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<<- | ||||
| init receivers | ||||
| layer=0</panel_attributes> | ||||
|     <additional_attributes>180.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>576</x> | ||||
|       <y>828</y> | ||||
|       <w>180</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<<- | ||||
| perform handshake</panel_attributes> | ||||
|     <additional_attributes>180.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>747</x> | ||||
|       <y>873</y> | ||||
|       <w>369</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<<- | ||||
| init handhake receivers | ||||
| layer=0</panel_attributes> | ||||
|     <additional_attributes>390.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>747</x> | ||||
|       <y>1071</y> | ||||
|       <w>369</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<<- | ||||
| init permanent receivers | ||||
| layer=0</panel_attributes> | ||||
|     <additional_attributes>390.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>1638</x> | ||||
|       <y>630</y> | ||||
|       <w>27</w> | ||||
|       <h>306</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=.</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;10.0;320.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>1638</x> | ||||
|       <y>918</y> | ||||
|       <w>18</w> | ||||
|       <h>558</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>bg=cyan</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>747</x> | ||||
|       <y>918</y> | ||||
|       <w>909</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<<- | ||||
| send login credentials | ||||
| layer=0</panel_attributes> | ||||
|     <additional_attributes>990.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>1872</x> | ||||
|       <y>630</y> | ||||
|       <w>27</w> | ||||
|       <h>306</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=.</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;10.0;320.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>1872</x> | ||||
|       <y>918</y> | ||||
|       <w>18</w> | ||||
|       <h>558</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>bg=gray</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>1881</x> | ||||
|       <y>918</y> | ||||
|       <w>423</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<<- | ||||
| use queries | ||||
| layer=0</panel_attributes> | ||||
|     <additional_attributes>450.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>2286</x> | ||||
|       <y>666</y> | ||||
|       <w>27</w> | ||||
|       <h>270</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=.</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;10.0;280.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>2286</x> | ||||
|       <y>918</y> | ||||
|       <w>18</w> | ||||
|       <h>558</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>bg=orange</panel_attributes> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>1647</x> | ||||
|       <y>918</y> | ||||
|       <w>243</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=<<- | ||||
| aquire data | ||||
| layer=0</panel_attributes> | ||||
|     <additional_attributes>250.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>1881</x> | ||||
|       <y>954</y> | ||||
|       <w>423</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=->> | ||||
| send query responses | ||||
| layer=0</panel_attributes> | ||||
|     <additional_attributes>450.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>1647</x> | ||||
|       <y>954</y> | ||||
|       <w>243</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=->> | ||||
| send aquired data | ||||
| layer=0</panel_attributes> | ||||
|     <additional_attributes>250.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>1107</x> | ||||
|       <y>990</y> | ||||
|       <w>549</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=->> | ||||
| send user, chats, pending messages, etc. | ||||
| layer=0</panel_attributes> | ||||
|     <additional_attributes>590.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>918</x> | ||||
|       <y>990</y> | ||||
|       <w>198</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=->> | ||||
| store data | ||||
| layer=0</panel_attributes> | ||||
|     <additional_attributes>200.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>99</x> | ||||
|       <y>729</y> | ||||
|       <w>306</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=-> | ||||
| start app</panel_attributes> | ||||
|     <additional_attributes>10.0;20.0;320.0;20.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>90</x> | ||||
|       <y>621</y> | ||||
|       <w>27</w> | ||||
|       <h>117</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=.</panel_attributes> | ||||
|     <additional_attributes>10.0;10.0;10.0;110.0</additional_attributes> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>UMLGeneric</id> | ||||
|     <coordinates> | ||||
|       <x>90</x> | ||||
|       <y>720</y> | ||||
|       <w>18</w> | ||||
|       <h>54</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes/> | ||||
|     <additional_attributes/> | ||||
|   </element> | ||||
|   <element> | ||||
|     <id>Relation</id> | ||||
|     <coordinates> | ||||
|       <x>747</x> | ||||
|       <y>1026</y> | ||||
|       <w>369</w> | ||||
|       <h>36</h> | ||||
|     </coordinates> | ||||
|     <panel_attributes>lt=->> | ||||
| online | ||||
| layer=0</panel_attributes> | ||||
|     <additional_attributes>390.0;20.0;10.0;20.0</additional_attributes> | ||||
|   </element> | ||||
| </diagram> | ||||
							
								
								
									
										7
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								pom.xml
									
									
									
									
									
								
							| @@ -28,6 +28,13 @@ | ||||
| 				</plugin> | ||||
| 			</plugins> | ||||
| 		</pluginManagement> | ||||
| 		<plugins> | ||||
| 			<plugin> | ||||
| 				<groupId>org.apache.maven.plugins</groupId> | ||||
| 				<artifactId>maven-surefire-plugin</artifactId> | ||||
| 				<version>3.0.0-M5</version> | ||||
| 			</plugin> | ||||
| 		</plugins> | ||||
| 	</build> | ||||
|  | ||||
| 	<modules> | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package envoy.server.data; | ||||
|  | ||||
| import java.time.Instant; | ||||
| import java.util.List; | ||||
| import java.util.*; | ||||
| import java.util.logging.Level; | ||||
|  | ||||
| import javax.persistence.*; | ||||
| @@ -121,8 +121,9 @@ public final class PersistenceManager { | ||||
| 		transaction(() -> { | ||||
|  | ||||
| 			// Remove this contact from the contact list of his contacts | ||||
| 			for (final var remainingContact : contact.getContacts()) | ||||
| 			for (final var remainingContact : contact.contacts) | ||||
| 				remainingContact.getContacts().remove(contact); | ||||
| 			contact.contacts.clear(); | ||||
| 		}); | ||||
| 		remove(contact); | ||||
| 	} | ||||
| @@ -223,6 +224,9 @@ public final class PersistenceManager { | ||||
| 	 * @since Envoy Server Standalone v0.2-beta | ||||
| 	 */ | ||||
| 	public List<Message> getPendingMessages(User user, Instant lastSync) { | ||||
| 		if (user == null) | ||||
| 			return new ArrayList<>(); | ||||
| 		lastSync = Objects.requireNonNullElse(lastSync, Instant.EPOCH); | ||||
| 		return entityManager.createNamedQuery(Message.getPending).setParameter("user", user) | ||||
| 			.setParameter("lastSeen", lastSync).getResultList(); | ||||
| 	} | ||||
| @@ -236,6 +240,9 @@ public final class PersistenceManager { | ||||
| 	 * @since Envoy Server Standalone v0.2-beta | ||||
| 	 */ | ||||
| 	public List<GroupMessage> getPendingGroupMessages(User user, Instant lastSync) { | ||||
| 		if (user == null) | ||||
| 			return new ArrayList<>(); | ||||
| 		lastSync = Objects.requireNonNullElse(lastSync, Instant.EPOCH); | ||||
| 		return entityManager.createNamedQuery(GroupMessage.getPendingGroupMsg) | ||||
| 			.setParameter("userId", user.getID()) | ||||
| 			.setParameter("lastSeen", lastSync) | ||||
| @@ -277,16 +284,18 @@ public final class PersistenceManager { | ||||
| 	 * @since Envoy Server v0.3-beta | ||||
| 	 */ | ||||
| 	public void addContactBidirectional(Contact contact1, Contact contact2) { | ||||
| 		if (!(contact1 == null || contact2 == null)) { | ||||
|  | ||||
| 		// Add users to each others contact list | ||||
| 		contact1.getContacts().add(contact2); | ||||
| 		contact2.getContacts().add(contact1); | ||||
| 			// Add users to each others contact list | ||||
| 			contact1.getContacts().add(contact2); | ||||
| 			contact2.getContacts().add(contact1); | ||||
|  | ||||
| 		// Synchronize changes with the database | ||||
| 		transaction(() -> { | ||||
| 			entityManager.merge(contact1); | ||||
| 			entityManager.merge(contact2); | ||||
| 		}); | ||||
| 			// Synchronize changes with the database | ||||
| 			transaction(() -> { | ||||
| 				entityManager.merge(contact1); | ||||
| 				entityManager.merge(contact2); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -308,16 +317,18 @@ public final class PersistenceManager { | ||||
| 	 * @since Envoy Server v0.3-beta | ||||
| 	 */ | ||||
| 	public void removeContactBidirectional(Contact contact1, Contact contact2) { | ||||
| 		if (!(contact1 == null || contact2 == null)) { | ||||
|  | ||||
| 		// Remove users from each others contact list | ||||
| 		contact1.getContacts().remove(contact2); | ||||
| 		contact2.getContacts().remove(contact1); | ||||
| 			// Remove users from each others contact list | ||||
| 			contact1.getContacts().remove(contact2); | ||||
| 			contact2.getContacts().remove(contact1); | ||||
|  | ||||
| 		// Synchronize changes with the database | ||||
| 		transaction(() -> { | ||||
| 			entityManager.merge(contact1); | ||||
| 			entityManager.merge(contact2); | ||||
| 		}); | ||||
| 			// Synchronize changes with the database | ||||
| 			transaction(() -> { | ||||
| 				entityManager.merge(contact1); | ||||
| 				entityManager.merge(contact2); | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -331,15 +342,36 @@ public final class PersistenceManager { | ||||
| 	} | ||||
|  | ||||
| 	private void persist(Object obj) { | ||||
| 		transaction(() -> entityManager.persist(obj)); | ||||
| 		try { | ||||
| 			transaction(() -> entityManager.persist(obj)); | ||||
| 		} catch (EntityExistsException e) { | ||||
| 			if (transaction.isActive()) | ||||
| 				transaction.rollback(); | ||||
| 			EnvoyLog.getLogger(PersistenceManager.class).log(Level.WARNING, | ||||
| 				String.format("Could not persist %s: entity exists already.", obj)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private void merge(Object obj) { | ||||
| 		transaction(() -> entityManager.merge(obj)); | ||||
| 		try { | ||||
| 			transaction(() -> entityManager.merge(obj)); | ||||
| 		} catch (IllegalArgumentException e) { | ||||
| 			if (transaction.isActive()) | ||||
| 				transaction.rollback(); | ||||
| 			EnvoyLog.getLogger(PersistenceManager.class).log(Level.WARNING, | ||||
| 				String.format("Could not merge %s: entity doesn't exist.", obj)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	private void remove(Object obj) { | ||||
| 		transaction(() -> entityManager.remove(obj)); | ||||
| 		try { | ||||
| 			transaction(() -> entityManager.remove(obj)); | ||||
| 		} catch (IllegalArgumentException e) { | ||||
| 			if (transaction.isActive()) | ||||
| 				transaction.rollback(); | ||||
| 			EnvoyLog.getLogger(PersistenceManager.class).log(Level.WARNING, | ||||
| 				String.format("Could not remove %s: entity didn't exist (for the database).", obj)); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -49,8 +49,10 @@ public final class ConnectionManager implements ISocketIdListener { | ||||
| 			// Notify contacts of this users offline-going | ||||
| 			final envoy.server.data.User user = | ||||
| 				PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID)); | ||||
| 			user.setLastSeen(Instant.now()); | ||||
| 			UserStatusChangeProcessor.updateUserStatus(user, UserStatus.OFFLINE); | ||||
| 			if (user != null) { | ||||
| 				user.setLastSeen(Instant.now()); | ||||
| 				UserStatusChangeProcessor.updateUserStatus(user, UserStatus.OFFLINE); | ||||
| 			} | ||||
|  | ||||
| 			// Remove the socket | ||||
| 			sockets.entrySet().removeIf(e -> e.getValue() == socketID); | ||||
|   | ||||
| @@ -33,7 +33,6 @@ public final class ObjectMessageProcessor implements IMessageProcessor { | ||||
| 		this.processors = processors; | ||||
| 	} | ||||
|  | ||||
| 	@SuppressWarnings("unchecked") | ||||
| 	@Override | ||||
| 	public void process(Message message, WriteProxy writeProxy) { | ||||
| 		try (ObjectInputStream in = | ||||
| @@ -45,23 +44,34 @@ public final class ObjectMessageProcessor implements IMessageProcessor { | ||||
| 				return; | ||||
| 			} | ||||
|  | ||||
| 			logger.fine("Received " + obj); | ||||
| 			logger.log(Level.INFO, "Received " + obj); | ||||
|  | ||||
| 			// Get processor and input class and process object | ||||
| 			for (@SuppressWarnings("rawtypes") | ||||
| 			ObjectProcessor p : processors) { | ||||
| 				Class<?> c = (Class<?>) ((ParameterizedType) p.getClass().getGenericInterfaces()[0]) | ||||
| 					.getActualTypeArguments()[0]; | ||||
| 				if (c.equals(obj.getClass())) | ||||
| 					try { | ||||
| 						p.process(c.cast(obj), message.socketId, new ObjectWriteProxy(writeProxy)); | ||||
| 						break; | ||||
| 					} catch (IOException e) { | ||||
| 						logger.log(Level.SEVERE, "Exception during processor execution: ", e); | ||||
| 					} | ||||
| 			} | ||||
| 			refer(message.socketId, writeProxy, obj); | ||||
| 		} catch (IOException | ClassNotFoundException e) { | ||||
| 			e.printStackTrace(); | ||||
| 			logger.log(Level.WARNING, | ||||
| 				"An exception occurred when reading in an object: " + e); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Executes the appropriate {@link ObjectProcessor} for the given input ({@code obj}), if any is | ||||
| 	 * present. | ||||
| 	 */ | ||||
| 	@SuppressWarnings("unchecked") | ||||
| 	private void refer(long socketID, WriteProxy writeProxy, Object obj) { | ||||
|  | ||||
| 		// Get processor and input class and process object | ||||
| 		for (@SuppressWarnings("rawtypes") | ||||
| 		ObjectProcessor p : processors) { | ||||
| 			Class<?> c = (Class<?>) ((ParameterizedType) p.getClass().getGenericInterfaces()[0]) | ||||
| 				.getActualTypeArguments()[0]; | ||||
| 			if (c.equals(obj.getClass())) | ||||
| 				try { | ||||
| 					p.process(c.cast(obj), socketID, new ObjectWriteProxy(writeProxy)); | ||||
| 					break; | ||||
| 				} catch (IOException e) { | ||||
| 					logger.log(Level.SEVERE, "Exception during processor execution: ", e); | ||||
| 				} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,7 @@ import static envoy.server.Startup.config; | ||||
|  | ||||
| import java.time.Instant; | ||||
| import java.util.Collections; | ||||
| import java.util.logging.Logger; | ||||
| import java.util.logging.*; | ||||
|  | ||||
| import javax.persistence.EntityExistsException; | ||||
|  | ||||
| @@ -15,6 +15,7 @@ import envoy.util.EnvoyLog; | ||||
|  | ||||
| import envoy.server.data.PersistenceManager; | ||||
| import envoy.server.net.*; | ||||
| import envoy.server.util.UserAuthenticationUtil; | ||||
|  | ||||
| /** | ||||
|  * @author Maximilian Käfer | ||||
| @@ -29,6 +30,15 @@ public final class GroupMessageProcessor implements ObjectProcessor<GroupMessage | ||||
|  | ||||
| 	@Override | ||||
| 	public void process(GroupMessage groupMessage, long socketID, ObjectWriteProxy writeProxy) { | ||||
|  | ||||
| 		// Check whether the message has the expected parameters | ||||
| 		if (!UserAuthenticationUtil.isExpectedUser(groupMessage.getSenderID(), socketID) | ||||
| 			|| persistenceManager.getContactByID(groupMessage.getRecipientID()) == null) { | ||||
| 			logger.log(Level.INFO, | ||||
| 				"Received a group message with invalid parameters"); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		groupMessage.nextStatus(); | ||||
|  | ||||
| 		// Update statuses to SENT / RECEIVED depending on online status | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import envoy.util.EnvoyLog; | ||||
|  | ||||
| import envoy.server.data.*; | ||||
| import envoy.server.net.*; | ||||
| import envoy.server.util.UserAuthenticationUtil; | ||||
|  | ||||
| /** | ||||
|  * @author Maximilian Käfer | ||||
| @@ -28,7 +29,17 @@ public final class GroupMessageStatusChangeProcessor | ||||
| 	@Override | ||||
| 	public void process(GroupMessageStatusChange statusChange, long socketID, | ||||
| 		ObjectWriteProxy writeProxy) { | ||||
|  | ||||
| 		// Check whether the message has the expected parameters | ||||
| 		if (!UserAuthenticationUtil.isExpectedUser(statusChange.getMemberID(), socketID)) { | ||||
| 			logger.log(Level.INFO, | ||||
| 				"Received a group message with invalid parameters"); | ||||
| 			return; | ||||
| 		} | ||||
| 		 | ||||
| 		GroupMessage gmsg = (GroupMessage) persistenceManager.getMessageByID(statusChange.getID()); | ||||
| 		if (gmsg == null) | ||||
| 			return; | ||||
|  | ||||
| 		// Any other status than READ is not supposed to be sent to the server | ||||
| 		if (statusChange.get() != MessageStatus.READ) { | ||||
|   | ||||
| @@ -24,6 +24,10 @@ public final class GroupResizeProcessor implements ObjectProcessor<GroupResize> | ||||
| 		final var	group	= persistenceManager.getGroupByID(groupResize.getGroupID()); | ||||
| 		final var	sender	= persistenceManager.getUserByID(groupResize.get().getID()); | ||||
|  | ||||
| 		// TODO: Inform the sender that this group has already been deleted | ||||
| 		if (group == null) | ||||
| 			return; | ||||
|  | ||||
| 		// Perform the desired operation | ||||
| 		switch (groupResize.getOperation()) { | ||||
| 			case ADD: | ||||
|   | ||||
| @@ -23,10 +23,11 @@ public final class IsTypingProcessor implements ObjectProcessor<IsTyping> { | ||||
| 		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); | ||||
| 			if (connectionManager.isOnline(event.get())) | ||||
| 				writeProxy.write(connectionManager.getSocketID(event.get()), | ||||
| 					new IsTyping(connectionManager.getUserIDBySocketID(socketID))); | ||||
| 		} else | ||||
| 			writeProxy.writeToOnlineContacts(contact.getContacts(), event); | ||||
| 			writeProxy.writeToOnlineContacts(contact.getContacts(), | ||||
| 				new IsTyping(connectionManager.getUserIDBySocketID(socketID))); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -40,6 +40,7 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred | ||||
| 		// Cache this write proxy for user-independent notifications | ||||
| 		UserStatusChangeProcessor.setWriteProxy(writeProxy); | ||||
|  | ||||
| 		// Check for compatible versions | ||||
| 		if (!VersionUtil.verifyCompatibility(credentials.getClientVersion())) { | ||||
| 			logger.info("The client has the wrong version."); | ||||
| 			writeProxy.write(socketID, new HandshakeRejection(WRONG_VERSION)); | ||||
| @@ -70,10 +71,10 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred | ||||
| 						writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN)); | ||||
| 						return; | ||||
| 					} | ||||
| 				} else | ||||
| 				} else if (!PasswordUtil.validate(credentials.getPassword(), | ||||
| 					user.getPasswordHash())) { | ||||
|  | ||||
| 				// Check the password hash | ||||
| 				if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) { | ||||
| 					// Check the password hash | ||||
| 					logger.info(user + " has entered the wrong password."); | ||||
| 					writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER)); | ||||
| 					return; | ||||
| @@ -101,7 +102,8 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred | ||||
| 				writeProxy.write(socketID, new HandshakeRejection(USERNAME_TAKEN)); | ||||
| 				return; | ||||
| 			} catch (final NoResultException e) { | ||||
| 				// Creation of a new user | ||||
|  | ||||
| 				// Create a new user | ||||
| 				user = new User(); | ||||
| 				user.setName(credentials.getIdentifier()); | ||||
| 				user.setLastSeen(Instant.now()); | ||||
| @@ -123,7 +125,6 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred | ||||
| 		// Process token request | ||||
| 		if (credentials.requestToken()) { | ||||
| 			String token; | ||||
|  | ||||
| 			if (user.getAuthToken() != null && user.getAuthTokenExpiration().isAfter(Instant.now())) | ||||
|  | ||||
| 				// Reuse existing token and delay expiration date | ||||
| @@ -140,6 +141,14 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred | ||||
| 			writeProxy.write(socketID, new NewAuthToken(token)); | ||||
| 		} | ||||
|  | ||||
| 		// Notify the user if a contact deletion has happened since he last logged in | ||||
| 		if (user.getLatestContactDeletion().isAfter(user.getLastSeen())) | ||||
| 			writeProxy.write(socketID, new ContactsChangedSinceLastLogin()); | ||||
|  | ||||
| 		// Complete the handshake | ||||
| 		writeProxy.write(socketID, user.toCommon()); | ||||
|  | ||||
| 		// Send pending (group) messages and status changes | ||||
| 		final var pendingMessages = | ||||
| 			PersistenceManager.getInstance().getPendingMessages(user, credentials.getLastSync()); | ||||
| 		pendingMessages.removeIf(GroupMessage.class::isInstance); | ||||
| @@ -164,8 +173,9 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred | ||||
| 					writeProxy.write(connectionManager.getSocketID(msg.getSender().getID()), | ||||
| 						new MessageStatusChange(msgCommon)); | ||||
| 				} | ||||
| 			} else | ||||
| 			} else { | ||||
| 				writeProxy.write(socketID, new MessageStatusChange(msgCommon)); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		final List<GroupMessage> pendingGroupMessages = PersistenceManager.getInstance() | ||||
| @@ -199,10 +209,11 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred | ||||
| 					} | ||||
|  | ||||
| 					PersistenceManager.getInstance().updateMessage(gmsg); | ||||
| 				} else | ||||
| 				} else { | ||||
|  | ||||
| 					// Just send the message without updating if it was received in the past | ||||
| 					writeProxy.write(socketID, gmsgCommon); | ||||
| 				} | ||||
| 			} else { | ||||
|  | ||||
| 				// Sending group message status changes | ||||
| @@ -222,11 +233,5 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred | ||||
| 					writeProxy.write(socketID, new MessageStatusChange(gmsgCommon)); | ||||
| 			} | ||||
| 		} | ||||
| 		// Notify the user if a contact deletion has happened since he last logged in | ||||
| 		if (user.getLatestContactDeletion().isAfter(user.getLastSeen())) | ||||
| 			writeProxy.write(socketID, new ContactsChangedSinceLastLogin()); | ||||
|  | ||||
| 		// Complete the handshake | ||||
| 		writeProxy.write(socketID, user.toCommon()); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import envoy.util.EnvoyLog; | ||||
|  | ||||
| import envoy.server.data.PersistenceManager; | ||||
| import envoy.server.net.*; | ||||
| import envoy.server.util.UserAuthenticationUtil; | ||||
|  | ||||
| /** | ||||
|  * This {@link ObjectProcessor} handles incoming {@link Message}s. | ||||
| @@ -29,6 +30,15 @@ public final class MessageProcessor implements ObjectProcessor<Message> { | ||||
|  | ||||
| 	@Override | ||||
| 	public void process(Message message, long socketID, ObjectWriteProxy writeProxy) { | ||||
|  | ||||
| 		// Check whether the message has the expected parameters | ||||
| 		if (!UserAuthenticationUtil.isExpectedUser(message.getSenderID(), socketID) | ||||
| 			|| persistenceManager.getContactByID(message.getRecipientID()) == null) { | ||||
| 			logger.log(Level.INFO, | ||||
| 				"Received a message with invalid parameters"); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		message.nextStatus(); | ||||
|  | ||||
| 		// Convert to server message | ||||
|   | ||||
| @@ -32,6 +32,8 @@ public final class MessageStatusChangeProcessor implements ObjectProcessor<Messa | ||||
| 		} | ||||
|  | ||||
| 		final var msg = persistenceManager.getMessageByID(statusChange.getID()); | ||||
| 		if (msg == null) | ||||
| 			return; | ||||
| 		msg.read(); | ||||
| 		persistenceManager.updateMessage(msg); | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import envoy.event.*; | ||||
| import envoy.util.EnvoyLog; | ||||
|  | ||||
| import envoy.server.data.PersistenceManager; | ||||
| import envoy.server.net.ObjectWriteProxy; | ||||
| import envoy.server.net.*; | ||||
| import envoy.server.util.PasswordUtil; | ||||
|  | ||||
| /** | ||||
| @@ -21,7 +21,8 @@ public final class PasswordChangeRequestProcessor | ||||
| 	public void process(PasswordChangeRequest event, long socketID, ObjectWriteProxy writeProxy) | ||||
| 		throws IOException { | ||||
| 		final var	persistenceManager		= PersistenceManager.getInstance(); | ||||
| 		final var	user					= persistenceManager.getUserByID(event.getID()); | ||||
| 		final var	user					= persistenceManager | ||||
| 			.getUserByID(ConnectionManager.getInstance().getUserIDBySocketID(socketID)); | ||||
| 		final var	logger					= | ||||
| 			EnvoyLog.getLogger(PasswordChangeRequestProcessor.class); | ||||
| 		final var	correctAuthentication	= | ||||
|   | ||||
| @@ -22,10 +22,16 @@ public final class UserOperationProcessor implements ObjectProcessor<UserOperati | ||||
| 	private static final PersistenceManager	persistenceManager	= PersistenceManager.getInstance(); | ||||
|  | ||||
| 	@Override | ||||
| 	public void process(UserOperation evt, long socketId, ObjectWriteProxy writeProxy) { | ||||
| 		final long	userID		= ConnectionManager.getInstance().getUserIDBySocketID(socketId); | ||||
| 	public void process(UserOperation evt, long socketID, ObjectWriteProxy writeProxy) { | ||||
| 		final long	userID		= ConnectionManager.getInstance().getUserIDBySocketID(socketID); | ||||
| 		final long	contactID	= evt.get().getID(); | ||||
| 		final var	sender		= persistenceManager.getUserByID(userID); | ||||
| 		final var	recipient	= persistenceManager.getUserByID(contactID); | ||||
|  | ||||
| 		// TODO: Inform the sender if the requested contact has already been deleted | ||||
| 		if (recipient == null) | ||||
| 			return; | ||||
|  | ||||
| 		final var sender = persistenceManager.getUserByID(userID); | ||||
| 		switch (evt.getOperationType()) { | ||||
| 			case ADD: | ||||
| 				logger.log(Level.FINE, | ||||
| @@ -45,7 +51,7 @@ public final class UserOperationProcessor implements ObjectProcessor<UserOperati | ||||
| 				sender.setLatestContactDeletion(Instant.now()); | ||||
|  | ||||
| 				// Notify the removed contact on next startup(s) of this deletion | ||||
| 				persistenceManager.getUserByID(contactID).setLatestContactDeletion(Instant.now()); | ||||
| 				recipient.setLatestContactDeletion(Instant.now()); | ||||
|  | ||||
| 				// Notify the removed contact if online | ||||
| 				if (connectionManager.isOnline(contactID)) | ||||
|   | ||||
| @@ -0,0 +1,24 @@ | ||||
| package envoy.server.util; | ||||
|  | ||||
| import envoy.server.net.ConnectionManager; | ||||
|  | ||||
| /** | ||||
|  * @author Leon Hofmeister | ||||
|  * @since Envoy Server v0.3-beta | ||||
|  */ | ||||
| public final class UserAuthenticationUtil { | ||||
|  | ||||
| 	private UserAuthenticationUtil() {} | ||||
|  | ||||
| 	/** | ||||
| 	 * Checks whether a user is really who he claims to be. | ||||
| 	 * | ||||
| 	 * @param expectedID the expected user ID | ||||
| 	 * @param socketID   the socket ID of the user making a request | ||||
| 	 * @return whether this user is who he claims to be | ||||
| 	 * @since Envoy Server v0.3-beta | ||||
| 	 */ | ||||
| 	public static boolean isExpectedUser(long expectedID, long socketID) { | ||||
| 		return ConnectionManager.getInstance().getUserIDBySocketID(socketID) == expectedID; | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user