Merge branch 'develop' into f/contacts
This commit is contained in:
		| @@ -6,6 +6,7 @@ import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.prefs.Preferences; | ||||
|  | ||||
| import envoy.client.data.Config; | ||||
| import envoy.client.ui.Color; | ||||
| import envoy.client.ui.Theme; | ||||
| import envoy.util.SerializationUtils; | ||||
|   | ||||
							
								
								
									
										61
									
								
								src/main/java/envoy/client/data/Cache.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/main/java/envoy/client/data/Cache.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| package envoy.client.data; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.LinkedList; | ||||
| import java.util.Queue; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.logging.Logger; | ||||
|  | ||||
| import envoy.client.util.EnvoyLog; | ||||
|  | ||||
| /** | ||||
|  * Stores elements in a queue to process them later.<br> | ||||
|  * <br> | ||||
|  * Project: <strong>envoy-client</strong><br> | ||||
|  * File: <strong>Cache.java</strong><br> | ||||
|  * Created: <strong>6 Feb 2020</strong><br> | ||||
|  * | ||||
|  * @param <T> the type of cached elements | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since Envoy v0.3-alpha | ||||
|  */ | ||||
| public class Cache<T> implements Consumer<T>, Serializable { | ||||
|  | ||||
| 	private final Queue<T>			elements	= new LinkedList<>(); | ||||
| 	private transient Consumer<T>	processor; | ||||
|  | ||||
| 	private static final Logger logger = EnvoyLog.getLogger(Cache.class.getSimpleName()); | ||||
| 	private static final long	serialVersionUID	= 7343544675545545076L; | ||||
|  | ||||
| 	/** | ||||
| 	 * Adds an element to the cache. | ||||
| 	 * | ||||
| 	 * @param element the element to add | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	@Override | ||||
| 	public void accept(T element) { | ||||
| 		logger.info(String.format("Adding element %s to cache", element)); | ||||
| 		elements.offer(element); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Sets the processor to which cached elements are relayed. | ||||
| 	 * | ||||
| 	 * @param processor the processor to set | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void setProcessor(Consumer<T> processor) { this.processor = processor; } | ||||
|  | ||||
| 	/** | ||||
| 	 * Relays all cached elements to the processor. | ||||
| 	 * | ||||
| 	 * @throws IllegalStateException if the processor is not initialized | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void relay() { | ||||
| 		if (processor == null) throw new IllegalStateException("Processor is not defined"); | ||||
| 		elements.forEach(processor::accept); | ||||
| 		elements.clear(); | ||||
| 	} | ||||
| } | ||||
| @@ -3,7 +3,7 @@ package envoy.client.data; | ||||
| import java.io.IOException; | ||||
| import java.io.Serializable; | ||||
|  | ||||
| import envoy.client.net.Client; | ||||
| import envoy.client.net.WriteProxy; | ||||
| import envoy.client.ui.list.ComponentListModel; | ||||
| import envoy.data.Message; | ||||
| import envoy.data.Message.MessageStatus; | ||||
| @@ -52,22 +52,20 @@ public class Chat implements Serializable { | ||||
| 	 * {@code READ} starting from the bottom and stopping once a read message is | ||||
| 	 * found. | ||||
| 	 * | ||||
| 	 * @param client the client instance used to notify the server about the message | ||||
| 	 *               status changes | ||||
| 	 * @param writeProxy the write proxy instance used to notify the server about | ||||
| 	 *                   the message status changes | ||||
| 	 * @throws IOException if a {@link MessageStatusChangeEvent} could not be | ||||
| 	 *                     delivered to the server | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void read(Client client) throws IOException { | ||||
| 	public void read(WriteProxy writeProxy) throws IOException { | ||||
| 		for (int i = model.size() - 1; i >= 0; --i) { | ||||
| 			final Message m = model.get(i); | ||||
| 			if (m.getSenderId() == recipient.getId()) { | ||||
| 				if (m.getStatus() == MessageStatus.READ) break; | ||||
| 				else { | ||||
| 					m.setStatus(MessageStatus.READ); | ||||
|  | ||||
| 					// TODO: Cache events in offline mode | ||||
| 					client.sendEvent(new MessageStatusChangeEvent(m)); | ||||
| 					writeProxy.writeMessageStatusChangeEvent(new MessageStatusChangeEvent(m)); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|   | ||||
| @@ -1,9 +1,12 @@ | ||||
| package envoy.client; | ||||
| package envoy.client.data; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| import java.util.*; | ||||
| import java.util.function.Function; | ||||
| import java.util.logging.Level; | ||||
| 
 | ||||
| import envoy.data.LoginCredentials; | ||||
| import envoy.exception.EnvoyException; | ||||
| 
 | ||||
| /** | ||||
| @@ -26,14 +29,15 @@ public class Config { | ||||
| 	private static Config config; | ||||
| 
 | ||||
| 	private Config() { | ||||
| 		items.put("server", new ConfigItem<>("server", "s", (input) -> input, null)); | ||||
| 		items.put("port", new ConfigItem<>("port", "p", (input) -> Integer.parseInt(input), null)); | ||||
| 		items.put("localDB", new ConfigItem<>("localDB", "db", (input) -> new File(input), new File("localDB"))); | ||||
| 		items.put("ignoreLocalDB", new ConfigItem<>("ignoreLocalDB", "nodb", (input) -> Boolean.parseBoolean(input), false)); | ||||
| 		items.put("homeDirectory", | ||||
| 				new ConfigItem<>("homeDirectory", "h", (input) -> new File(input), new File(System.getProperty("user.home"), ".envoy"))); | ||||
| 		items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", (input) -> Level.parse(input), Level.CONFIG)); | ||||
| 		items.put("consoleLevelBarrier", new ConfigItem<>("consoleLevelBarrier", "cb", (input) -> Level.parse(input), Level.FINEST)); | ||||
| 		items.put("server", new ConfigItem<>("server", "s", Function.identity(), null, true)); | ||||
| 		items.put("port", new ConfigItem<>("port", "p", Integer::parseInt, null, true)); | ||||
| 		items.put("localDB", new ConfigItem<>("localDB", "db", File::new, new File("localDB"), true)); | ||||
| 		items.put("ignoreLocalDB", new ConfigItem<>("ignoreLocalDB", "nodb", Boolean::parseBoolean, false, false)); | ||||
| 		items.put("homeDirectory", new ConfigItem<>("homeDirectory", "h", File::new, new File(System.getProperty("user.home"), ".envoy"), true)); | ||||
| 		items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", Level::parse, Level.CONFIG, true)); | ||||
| 		items.put("consoleLevelBarrier", new ConfigItem<>("consoleLevelBarrier", "cb", Level::parse, Level.FINEST, true)); | ||||
| 		items.put("user", new ConfigItem<>("user", "u", Function.identity())); | ||||
| 		items.put("password", new ConfigItem<>("password", "pw", String::toCharArray)); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| @@ -95,10 +99,12 @@ public class Config { | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return {@code true} if server, port and localDB directory are known. | ||||
| 	 * @return {@code true} if all mandatory config items are initialized | ||||
| 	 * @since Envoy v0.1-alpha | ||||
| 	 */ | ||||
| 	public boolean isInitialized() { return items.values().stream().map(ConfigItem::get).noneMatch(Objects::isNull); } | ||||
| 	public boolean isInitialized() { | ||||
| 		return items.values().stream().filter(ConfigItem::isMandatory).map(ConfigItem::get).noneMatch(Objects::isNull); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return the host name of the Envoy server | ||||
| @@ -141,4 +147,35 @@ public class Config { | ||||
| 	 * @since Envoy v0.2-alpha | ||||
| 	 */ | ||||
| 	public Level getConsoleLevelBarrier() { return (Level) items.get("consoleLevelBarrier").get(); } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return the user name | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public String getUser() { return (String) items.get("user").get(); } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return the password | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public char[] getPassword() { return (char[]) items.get("password").get(); } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return {@code true} if user name and password are set | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public boolean hasLoginCredentials() { return getUser() != null && getPassword() != null; } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return login credentials for the specified user name and password, without | ||||
| 	 *         the registration option | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public LoginCredentials getLoginCredentials() { | ||||
| 		try { | ||||
| 			return new LoginCredentials(getUser(), getPassword(), false); | ||||
| 		} catch (NoSuchAlgorithmException e) { | ||||
| 			return null; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package envoy.client; | ||||
| package envoy.client.data; | ||||
| 
 | ||||
| import java.util.function.Function; | ||||
| 
 | ||||
| @@ -9,49 +9,70 @@ import java.util.function.Function; | ||||
|  * Project: <strong>envoy-clientChess</strong><br> | ||||
|  * File: <strong>ConfigItem.javaEvent.java</strong><br> | ||||
|  * Created: <strong>21.12.2019</strong><br> | ||||
|  *  | ||||
|  * | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @param <T> the type of the config item's value | ||||
|  * @since Envoy v0.2-alpha | ||||
|  */ | ||||
| public class ConfigItem<T> { | ||||
| 
 | ||||
| 	private String				commandLong, commandShort; | ||||
| 	private Function<String, T>	parseFunction; | ||||
| 	private T					value; | ||||
| 	private final String				commandLong, commandShort; | ||||
| 	private final Function<String, T>	parseFunction; | ||||
| 	private final boolean				mandatory; | ||||
| 
 | ||||
| 	private T value; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Initializes a {@link ConfigItem} | ||||
| 	 *  | ||||
| 	 * Initializes a {@link ConfigItem}. | ||||
| 	 * | ||||
| 	 * @param commandLong   the long command line argument to set this value | ||||
| 	 * @param commandShort  the short command line argument to set this value | ||||
| 	 * @param parseFunction the {@code Function<String, T>} that parses the value | ||||
| 	 *                      from a string | ||||
| 	 * @param defaultValue  the optional default value to set before parsing | ||||
| 	 * @param mandatory     indicated that this config item must be initialized with | ||||
| 	 *                      a non-null value | ||||
| 	 * @since Envoy v0.2-alpha | ||||
| 	 */ | ||||
| 	public ConfigItem(String commandLong, String commandShort, Function<String, T> parseFunction, T defaultValue) { | ||||
| 	public ConfigItem(String commandLong, String commandShort, Function<String, T> parseFunction, T defaultValue, boolean mandatory) { | ||||
| 		this.commandLong	= commandLong; | ||||
| 		this.commandShort	= commandShort; | ||||
| 		this.parseFunction	= parseFunction; | ||||
| 		this.mandatory		= mandatory; | ||||
| 		value				= defaultValue; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Parses this {@ConfigItem}'s value from a string | ||||
| 	 * Initializes an optional {@link ConfigItem} without a default value. | ||||
| 	 * | ||||
| 	 * @param commandLong   the long command line argument to set this value | ||||
| 	 * @param commandShort  the short command line argument to set this value | ||||
| 	 * @param parseFunction the {@code Function<String, T>} that parses the value | ||||
| 	 *                      from a string | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public ConfigItem(String commandLong, String commandShort, Function<String, T> parseFunction) { | ||||
| 		this(commandLong, commandShort, parseFunction, null, false); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Parses this {@ConfigItem}'s value from a string. | ||||
| 	 * | ||||
| 	 * @param input the string to parse from | ||||
| 	 * @since Envoy v0.2-alpha | ||||
| 	 */ | ||||
| 	public void parse(String input) { value = parseFunction.apply(input); } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return The long command line argument to set the value of this {@link ConfigItem} | ||||
| 	 * @return The long command line argument to set the value of this | ||||
| 	 *         {@link ConfigItem} | ||||
| 	 * @since Envoy v0.2-alpha | ||||
| 	 */ | ||||
| 	public String getCommandLong() { return commandLong; } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return The short command line argument to set the value of this {@link ConfigItem} | ||||
| 	 * @return The short command line argument to set the value of this | ||||
| 	 *         {@link ConfigItem} | ||||
| 	 * @since Envoy v0.2-alpha | ||||
| 	 */ | ||||
| 	public String getCommandShort() { return commandShort; } | ||||
| @@ -61,4 +82,11 @@ public class ConfigItem<T> { | ||||
| 	 * @since Envoy v0.2-alpha | ||||
| 	 */ | ||||
| 	public T get() { return value; } | ||||
| 
 | ||||
| 	/** | ||||
| 	 * @return {@code true} if this {@link ConfigItem} is mandatory for successful | ||||
| 	 *         application initialization | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public boolean isMandatory() { return mandatory; } | ||||
| } | ||||
| @@ -3,7 +3,9 @@ package envoy.client.data; | ||||
| import java.util.*; | ||||
|  | ||||
| import envoy.data.IdGenerator; | ||||
| import envoy.data.Message; | ||||
| import envoy.data.User; | ||||
| import envoy.event.MessageStatusChangeEvent; | ||||
|  | ||||
| /** | ||||
|  * Stores information about the current {@link User} and their {@link Chat}s. | ||||
| @@ -18,10 +20,12 @@ import envoy.data.User; | ||||
|  */ | ||||
| public abstract class LocalDb { | ||||
|  | ||||
| 	protected User				user; | ||||
| 	protected Map<String, User>	users	= new HashMap<>(); | ||||
| 	protected List<Chat>		chats	= new ArrayList<>(); | ||||
| 	protected IdGenerator		idGenerator; | ||||
| 	protected User								user; | ||||
| 	protected Map<String, User>					users	= new HashMap<>(); | ||||
| 	protected List<Chat>						chats	= new ArrayList<>(); | ||||
| 	protected IdGenerator						idGenerator; | ||||
| 	protected Cache<Message>					messageCache	= new Cache<>(); | ||||
| 	protected Cache<MessageStatusChangeEvent>	statusCache		= new Cache<>(); | ||||
|  | ||||
| 	/** | ||||
| 	 * Initializes a storage space for a user-specific list of chats. | ||||
| @@ -48,12 +52,12 @@ public abstract class LocalDb { | ||||
| 	public void loadUsers() throws Exception {} | ||||
|  | ||||
| 	/** | ||||
| 	 * Loads all chat data of the client user. | ||||
| 	 * Loads all data of the client user. | ||||
| 	 * | ||||
| 	 * @throws Exception if the loading process failed | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void loadChats() throws Exception {} | ||||
| 	public void loadUserData() throws Exception {} | ||||
|  | ||||
| 	/** | ||||
| 	 * Loads the ID generator. Any exception thrown during this process is ignored. | ||||
| @@ -115,4 +119,28 @@ public abstract class LocalDb { | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public boolean hasIdGenerator() { return idGenerator != null; } | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the offline message cache | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public Cache<Message> getMessageCache() { return messageCache; } | ||||
|  | ||||
| 	/** | ||||
| 	 * @param messageCache the offline message cache to set | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void setMessageCache(Cache<Message> messageCache) { this.messageCache = messageCache; } | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the offline status cache | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public Cache<MessageStatusChangeEvent> getStatusCache() { return statusCache; } | ||||
|  | ||||
| 	/** | ||||
| 	 * @param statusCache the offline status cache to set | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void setStatusCache(Cache<MessageStatusChangeEvent> statusCache) { this.statusCache = statusCache; } | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import java.io.IOException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
|  | ||||
| import envoy.client.ConfigItem; | ||||
| import envoy.data.IdGenerator; | ||||
| import envoy.util.SerializationUtils; | ||||
|  | ||||
| @@ -23,7 +22,7 @@ import envoy.util.SerializationUtils; | ||||
|  */ | ||||
| public class PersistentLocalDb extends LocalDb { | ||||
|  | ||||
| 	private File localDBDir, localDBFile, usersFile, idGeneratorFile; | ||||
| 	private File localDBDir, localDBFile, usersFile, idGeneratorFile, messageCacheFile, statusCacheFile; | ||||
|  | ||||
| 	/** | ||||
| 	 * Initializes an empty local database without a directory. All changes made to | ||||
| @@ -40,18 +39,18 @@ public class PersistentLocalDb extends LocalDb { | ||||
| 	 * Constructs an empty local database. To serialize any chats to the file | ||||
| 	 * system, call {@link PersistentLocalDb#initializeUserStorage()}. | ||||
| 	 * | ||||
| 	 * @param localDBDir the directory in which to store users and chats | ||||
| 	 * @param localDbDir the directory in which to store users and chats | ||||
| 	 * @throws IOException if the PersistentLocalDb could not be initialized | ||||
| 	 * @since Envoy v0.1-alpha | ||||
| 	 */ | ||||
| 	public PersistentLocalDb(File localDBDir) throws IOException { | ||||
| 		this.localDBDir = localDBDir; | ||||
| 	public PersistentLocalDb(File localDbDir) throws IOException { | ||||
| 		localDBDir = localDbDir; | ||||
|  | ||||
| 		// Initialize local database directory | ||||
| 		if (localDBDir.exists() && !localDBDir.isDirectory()) | ||||
| 			throw new IOException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath())); | ||||
| 		usersFile		= new File(localDBDir, "users.db"); | ||||
| 		idGeneratorFile	= new File(localDBDir, "id_generator.db"); | ||||
| 		if (localDbDir.exists() && !localDbDir.isDirectory()) | ||||
| 			throw new IOException(String.format("LocalDbDir '%s' is not a directory!", localDbDir.getAbsolutePath())); | ||||
| 		usersFile		= new File(localDbDir, "users.db"); | ||||
| 		idGeneratorFile	= new File(localDbDir, "id_generator.db"); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| @@ -63,7 +62,9 @@ public class PersistentLocalDb extends LocalDb { | ||||
| 	@Override | ||||
| 	public void initializeUserStorage() { | ||||
| 		if (user == null) throw new NullPointerException("Client user is null"); | ||||
| 		localDBFile = new File(localDBDir, user.getId() + ".db"); | ||||
| 		localDBFile			= new File(localDBDir, user.getId() + ".db"); | ||||
| 		messageCacheFile	= new File(localDBDir, user.getId() + "_message_cache.db"); | ||||
| 		statusCacheFile		= new File(localDBDir, user.getId() + "_status_cache.db"); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| @@ -71,8 +72,12 @@ public class PersistentLocalDb extends LocalDb { | ||||
| 		// Save users | ||||
| 		SerializationUtils.write(usersFile, users); | ||||
|  | ||||
| 		// Save chats | ||||
| 		if (user != null) SerializationUtils.write(localDBFile, chats); | ||||
| 		// Save user data | ||||
| 		if (user != null) { | ||||
| 			SerializationUtils.write(localDBFile, chats); | ||||
| 			SerializationUtils.write(messageCacheFile, messageCache); | ||||
| 			SerializationUtils.write(statusCacheFile, statusCache); | ||||
| 		} | ||||
|  | ||||
| 		// Save id generator | ||||
| 		if (hasIdGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator); | ||||
| @@ -82,7 +87,11 @@ public class PersistentLocalDb extends LocalDb { | ||||
| 	public void loadUsers() throws ClassNotFoundException, IOException { users = SerializationUtils.read(usersFile, HashMap.class); } | ||||
|  | ||||
| 	@Override | ||||
| 	public void loadChats() throws ClassNotFoundException, IOException { chats = SerializationUtils.read(localDBFile, ArrayList.class); } | ||||
| 	public void loadUserData() throws ClassNotFoundException, IOException { | ||||
| 		chats			= SerializationUtils.read(localDBFile, ArrayList.class); | ||||
| 		messageCache	= SerializationUtils.read(messageCacheFile, Cache.class); | ||||
| 		statusCache		= SerializationUtils.read(statusCacheFile, Cache.class); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void loadIdGenerator() { | ||||
|   | ||||
| @@ -9,7 +9,8 @@ import java.util.logging.Logger; | ||||
|  | ||||
| import javax.naming.TimeLimitExceededException; | ||||
|  | ||||
| import envoy.client.Config; | ||||
| import envoy.client.data.Cache; | ||||
| import envoy.client.data.Config; | ||||
| import envoy.client.data.LocalDb; | ||||
| import envoy.client.event.SearchResultEvent; | ||||
| import envoy.client.util.EnvoyLog; | ||||
| @@ -51,16 +52,17 @@ public class Client implements Closeable { | ||||
| 	 * will block for up to 5 seconds. If the handshake does exceed this time limit, | ||||
| 	 * an exception is thrown. | ||||
| 	 * | ||||
| 	 * @param credentials the login credentials of the user | ||||
| 	 * @param localDb     the local database used to persist the current | ||||
| 	 *                    {@link IdGenerator} | ||||
| 	 * @return a message cache containing all unread messages from the server that | ||||
| 	 *         can be relayed after initialization | ||||
| 	 * @param credentials          the login credentials of the user | ||||
| 	 * @param localDb              the local database used to persist the current | ||||
| 	 *                             {@link IdGenerator} | ||||
| 	 * @param receivedMessageCache a message cache containing all unread messages | ||||
| 	 *                             from the server that can be relayed after | ||||
| 	 *                             initialization | ||||
| 	 * @throws Exception if the online mode could not be entered or the request | ||||
| 	 *                   failed for some other reason | ||||
| 	 * @since Envoy v0.2-alpha | ||||
| 	 */ | ||||
| 	public MessageCache onlineInit(LoginCredentials credentials, LocalDb localDb) throws Exception { | ||||
| 	public void onlineInit(LoginCredentials credentials, LocalDb localDb, Cache<Message> receivedMessageCache) throws Exception { | ||||
| 		// Establish TCP connection | ||||
| 		logger.info(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort())); | ||||
| 		socket = new Socket(config.getServer(), config.getPort()); | ||||
| @@ -69,13 +71,10 @@ public class Client implements Closeable { | ||||
| 		// Create message receiver | ||||
| 		receiver = new Receiver(socket.getInputStream()); | ||||
|  | ||||
| 		// Create cache for unread messages | ||||
| 		final MessageCache cache = new MessageCache(); | ||||
|  | ||||
| 		// Register user creation processor, contact list processor and message cache | ||||
| 		receiver.registerProcessor(User.class, sender -> { logger.info("Acquired user object " + sender); this.sender = sender; }); | ||||
| 		receiver.registerProcessor(Contacts.class, contacts -> { logger.info("Acquired contacts object " + contacts); this.contacts = contacts; }); | ||||
| 		receiver.registerProcessor(Message.class, cache); | ||||
| 		receiver.registerProcessor(Message.class, receivedMessageCache); | ||||
|  | ||||
| 		// Start receiver | ||||
| 		new Thread(receiver).start(); | ||||
| @@ -98,15 +97,20 @@ public class Client implements Closeable { | ||||
| 		receiver.removeAllProcessors(); | ||||
|  | ||||
| 		// Register processors for message and status handling | ||||
|  | ||||
| 		// Process incoming messages | ||||
| 		final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor(); | ||||
| 		receiver.registerProcessor(Message.class, receivedMessageProcessor); | ||||
|  | ||||
| 		// Relay cached unread messages | ||||
| 		cache.setProcessor(receivedMessageProcessor); | ||||
| 		receivedMessageCache.setProcessor(receivedMessageProcessor); | ||||
|  | ||||
| 		// Process message status changes | ||||
| 		receiver.registerProcessor(MessageStatusChangeEvent.class, new MessageStatusChangeEventProcessor()); | ||||
|  | ||||
| 		// Process user status changes | ||||
| 		receiver.registerProcessor(UserStatusChangeEvent.class, new UserStatusChangeProcessor(this)); | ||||
|  | ||||
| 		// Process message ID generation | ||||
| 		receiver.registerProcessor(IdGenerator.class, localDb::setIdGenerator); | ||||
|  | ||||
| @@ -115,10 +119,19 @@ public class Client implements Closeable { | ||||
|  | ||||
| 		// Request a generator if none is present or the existing one is consumed | ||||
| 		if (!localDb.hasIdGenerator() || !localDb.getIdGenerator().hasNext()) requestIdGenerator(); | ||||
|  | ||||
| 		return cache; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Creates a new write proxy that uses this client to communicate with the | ||||
| 	 * server. | ||||
| 	 * | ||||
| 	 * @param localDb the local database that the write proxy will use to access | ||||
| 	 *                caches | ||||
| 	 * @return a new write proxy | ||||
| 	 * @since Envoy Client v0.3-alpha | ||||
| 	 */ | ||||
| 	public WriteProxy createWriteProxy(LocalDb localDb) { return new WriteProxy(this, localDb); } | ||||
|  | ||||
| 	/** | ||||
| 	 * Sends a message to the server. The message's status will be incremented once | ||||
| 	 * it was delivered successfully. | ||||
| @@ -197,4 +210,16 @@ public class Client implements Closeable { | ||||
| 	 * @since Envoy v0.2-alpha | ||||
| 	 */ | ||||
| 	public boolean isOnline() { return online; } | ||||
| } | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the contacts of this {@link Client} | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public Contacts getContacts() { return contacts; } | ||||
|  | ||||
| 	/** | ||||
| 	 * @param contacts the contacts to set | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void setContacts(Contacts contacts) { this.contacts = contacts; } | ||||
| } | ||||
|   | ||||
| @@ -1,54 +0,0 @@ | ||||
| package envoy.client.net; | ||||
|  | ||||
| import java.util.LinkedList; | ||||
| import java.util.Queue; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.logging.Logger; | ||||
|  | ||||
| import envoy.client.util.EnvoyLog; | ||||
| import envoy.data.Message; | ||||
|  | ||||
| /** | ||||
|  * Stores messages in a queue until the application initialization is complete. | ||||
|  * The messages can then be relayed to a processor.<br> | ||||
|  * <br> | ||||
|  * Project: <strong>envoy-client</strong><br> | ||||
|  * File: <strong>MessageCache.java</strong><br> | ||||
|  * Created: <strong>4 Feb 2020</strong><br> | ||||
|  * | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since Envoy v0.3-alpha | ||||
|  */ | ||||
| public class MessageCache implements Consumer<Message> { | ||||
|  | ||||
| 	private final Queue<Message>	messages	= new LinkedList<>(); | ||||
| 	private Consumer<Message>		processor; | ||||
|  | ||||
| 	private static final Logger logger = EnvoyLog.getLogger(MessageCache.class.getSimpleName()); | ||||
|  | ||||
| 	/** | ||||
| 	 * Adds a message to the cache. | ||||
| 	 * | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	@Override | ||||
| 	public void accept(Message message) { | ||||
| 		logger.info(String.format("Adding message %s to cache", message)); | ||||
| 		messages.add(message); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Sets the processor to which messages are relayed. | ||||
| 	 * | ||||
| 	 * @param processor the processor to set | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void setProcessor(Consumer<Message> processor) { this.processor = processor; } | ||||
|  | ||||
| 	/** | ||||
| 	 * Relays all cached messages to the processor. | ||||
| 	 * | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void relayMessages() { messages.forEach(processor::accept); } | ||||
| } | ||||
| @@ -0,0 +1,37 @@ | ||||
| package envoy.client.net; | ||||
|  | ||||
| import java.util.function.Consumer; | ||||
| import java.util.logging.Logger; | ||||
|  | ||||
| import envoy.client.util.EnvoyLog; | ||||
| import envoy.event.EventBus; | ||||
| import envoy.event.UserStatusChangeEvent; | ||||
|  | ||||
| /** | ||||
|  * Project: <strong>envoy-client</strong><br> | ||||
|  * File: <strong>UserStatusChangeProcessor.java</strong><br> | ||||
|  * Created: <strong>2 Feb 2020</strong><br> | ||||
|  * | ||||
|  * @author Leon Hofmeister | ||||
|  * @since Envoy v0.3-alpha | ||||
|  */ | ||||
| public class UserStatusChangeProcessor implements Consumer<UserStatusChangeEvent> { | ||||
|  | ||||
| 	private Client client; | ||||
|  | ||||
| 	private static final Logger logger = EnvoyLog.getLogger(UserStatusChangeProcessor.class.getSimpleName()); | ||||
|  | ||||
| 	/** | ||||
| 	 * @param client the {@link Client} who receives an | ||||
| 	 *               {@link UserStatusChangeEvent} | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public UserStatusChangeProcessor(Client client) { this.client = client; } | ||||
|  | ||||
| 	@Override | ||||
| 	public void accept(UserStatusChangeEvent evt) { | ||||
| 		logger.info("Received " + evt); | ||||
| 		client.getContacts().getContacts().stream().filter((user) -> user.getId() == evt.getId()).findFirst().get().setStatus(evt.get()); | ||||
| 		EventBus.getInstance().dispatch(evt); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										103
									
								
								src/main/java/envoy/client/net/WriteProxy.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/main/java/envoy/client/net/WriteProxy.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| package envoy.client.net; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.util.logging.Level; | ||||
| import java.util.logging.Logger; | ||||
|  | ||||
| import envoy.client.data.LocalDb; | ||||
| import envoy.client.util.EnvoyLog; | ||||
| import envoy.data.Message; | ||||
| import envoy.event.MessageStatusChangeEvent; | ||||
|  | ||||
| /** | ||||
|  * Implements methods to send {@link Message}s and | ||||
|  * {@link MessageStatusChangeEvent}s to the server or cache them inside a | ||||
|  * {@link LocalDb} depending on the online status.<br> | ||||
|  * <br> | ||||
|  * Project: <strong>envoy-client</strong><br> | ||||
|  * File: <strong>WriteProxy.java</strong><br> | ||||
|  * Created: <strong>6 Feb 2020</strong><br> | ||||
|  * | ||||
|  * @author Kai S. K. Engelbart | ||||
|  * @since Envoy v0.3-alpha | ||||
|  */ | ||||
| public class WriteProxy { | ||||
|  | ||||
| 	private final Client	client; | ||||
| 	private final LocalDb	localDb; | ||||
|  | ||||
| 	private static final Logger logger = EnvoyLog.getLogger(WriteProxy.class.getSimpleName()); | ||||
|  | ||||
| 	/** | ||||
| 	 * Initializes a write proxy using a client and a local database. The | ||||
| 	 * corresponding cache processors are injected into the caches. | ||||
| 	 * | ||||
| 	 * @param client  the client used to send messages and message status change | ||||
| 	 *                events | ||||
| 	 * @param localDb the local database used to cache messages and message status | ||||
| 	 *                change events | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public WriteProxy(Client client, LocalDb localDb) { | ||||
| 		this.client		= client; | ||||
| 		this.localDb	= localDb; | ||||
|  | ||||
| 		// Initialize cache processors for messages and message status change events | ||||
| 		localDb.getMessageCache().setProcessor(msg -> { | ||||
| 			try { | ||||
| 				client.sendMessage(msg); | ||||
| 			} catch (IOException e) { | ||||
| 				logger.log(Level.SEVERE, "Could not send cached message", e); | ||||
| 			} | ||||
| 		}); | ||||
| 		localDb.getStatusCache().setProcessor(evt -> { | ||||
| 			try { | ||||
| 				client.sendEvent(evt); | ||||
| 			} catch (IOException e) { | ||||
| 				logger.log(Level.SEVERE, "Could not send cached message status change event", e); | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Sends cached {@link Message}s and {@link MessageStatusChangeEvent}s to the | ||||
| 	 * server. | ||||
| 	 * | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void flushCache() { | ||||
| 		logger.info("Sending cached messages and message status change events..."); | ||||
|  | ||||
| 		// Send messages | ||||
| 		localDb.getMessageCache().relay(); | ||||
|  | ||||
| 		// Send message status change events | ||||
| 		localDb.getStatusCache().relay(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Delivers a message to the server if online. Otherwise the message is cached | ||||
| 	 * inside the local database. | ||||
| 	 * | ||||
| 	 * @param message the message to send | ||||
| 	 * @throws IOException if the message could not be sent | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void writeMessage(Message message) throws IOException { | ||||
| 		if (client.isOnline()) client.sendMessage(message); | ||||
| 		else localDb.getMessageCache().accept(message); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Delivers a message status change event to the server if online. Otherwise the | ||||
| 	 * event is cached inside the local database. | ||||
| 	 * | ||||
| 	 * @param evt the event to send | ||||
| 	 * @throws IOException if the event could not be sent | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void writeMessageStatusChangeEvent(MessageStatusChangeEvent evt) throws IOException { | ||||
| 		if (client.isOnline()) client.sendEvent(evt); | ||||
| 		else localDb.getStatusCache().accept(evt); | ||||
| 	} | ||||
| } | ||||
| @@ -17,6 +17,7 @@ import envoy.client.data.Chat; | ||||
| import envoy.client.data.LocalDb; | ||||
| import envoy.client.event.*; | ||||
| import envoy.client.net.Client; | ||||
| import envoy.client.net.WriteProxy; | ||||
| import envoy.client.ui.list.ComponentList; | ||||
| import envoy.client.ui.list.ComponentListModel; | ||||
| import envoy.client.ui.settings.SettingsScreen; | ||||
| @@ -41,8 +42,9 @@ import envoy.event.ContactOperationEvent.Operation; | ||||
| public class ChatWindow extends JFrame { | ||||
|  | ||||
| 	// User specific objects | ||||
| 	private Client	client; | ||||
| 	private LocalDb	localDb; | ||||
| 	private Client		client; | ||||
| 	private WriteProxy	writeProxy; | ||||
| 	private LocalDb		localDb; | ||||
|  | ||||
| 	// GUI components | ||||
| 	private JPanel					contentPane				= new JPanel(); | ||||
| @@ -359,6 +361,9 @@ public class ChatWindow extends JFrame { | ||||
| 		// Listen to theme changes | ||||
| 		EventBus.getInstance().register(ThemeChangeEvent.class, (evt) -> applyTheme((Theme) evt.get())); | ||||
|  | ||||
| 		// Listen to user status changes | ||||
| 		EventBus.getInstance().register(UserStatusChangeEvent.class, (evt) -> { userList.revalidate(); userList.repaint(); }); | ||||
|  | ||||
| 		// Listen to received messages | ||||
| 		EventBus.getInstance().register(MessageCreationEvent.class, (evt) -> { | ||||
| 			Message	message	= ((MessageCreationEvent) evt).get(); | ||||
| @@ -498,8 +503,7 @@ public class ChatWindow extends JFrame { | ||||
| 				.build(); | ||||
|  | ||||
| 			// Send message | ||||
| 			// TODO: Store offline messages | ||||
| 			client.sendMessage(message); | ||||
| 			writeProxy.writeMessage(message); | ||||
|  | ||||
| 			// Add message to PersistentLocalDb and update UI | ||||
| 			currentChat.appendMessage(message); | ||||
| @@ -544,7 +548,7 @@ public class ChatWindow extends JFrame { | ||||
|  | ||||
| 	private void readCurrentChat() { | ||||
| 		try { | ||||
| 			currentChat.read(client); | ||||
| 			currentChat.read(writeProxy); | ||||
| 			messageList.synchronizeModel(); | ||||
| 		} catch (IOException e) { | ||||
| 			e.printStackTrace(); | ||||
| @@ -571,24 +575,23 @@ public class ChatWindow extends JFrame { | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * Sets the {@link Client} used by this {@link ChatWindow}. | ||||
| 	 * Initializes the components responsible server communication and | ||||
| 	 * persistence.<br> | ||||
| 	 * <br> | ||||
| 	 * This will trigger the display of the contact list. | ||||
| 	 * | ||||
| 	 * @param client the {@link Client} used to send and receive messages | ||||
| 	 * @since Envoy v0.2-alpha | ||||
| 	 * @param client     the client used to send and receive messages | ||||
| 	 * @param localDb    the local database used to manage stored messages | ||||
| 	 *                   and users | ||||
| 	 * @param writeProxy the write proxy used to send messages and status change | ||||
| 	 *                   events to the server or cache them inside the local | ||||
| 	 *                   database | ||||
| 	 * @since Envoy v0.3-alpha | ||||
| 	 */ | ||||
| 	public void setClient(Client client) { this.client = client; } | ||||
|  | ||||
| 	/** | ||||
| 	 * Sets the {@link LocalDb} used by this {@link ChatWindow}. After | ||||
| 	 * invoking this | ||||
| 	 * method, users and chats will be loaded from the database into the GUI. | ||||
| 	 * | ||||
| 	 * @param localDb the {@link LocalDb} used to manage stored messages | ||||
| 	 *                and users | ||||
| 	 * @since Envoy v0.2-alpha | ||||
| 	 */ | ||||
| 	public void setLocalDB(LocalDb localDb) { | ||||
| 		this.localDb = localDb; | ||||
| 	public void initContent(Client client, LocalDb localDb, WriteProxy writeProxy) { | ||||
| 		this.client		= client; | ||||
| 		this.localDb	= localDb; | ||||
| 		this.writeProxy	= writeProxy; | ||||
| 		loadUsersAndChats(); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -11,15 +11,13 @@ import javax.swing.JFrame; | ||||
| import javax.swing.JOptionPane; | ||||
| import javax.swing.SwingUtilities; | ||||
|  | ||||
| import envoy.client.Config; | ||||
| import envoy.client.Settings; | ||||
| import envoy.client.data.LocalDb; | ||||
| import envoy.client.data.PersistentLocalDb; | ||||
| import envoy.client.data.TransientLocalDb; | ||||
| import envoy.client.data.*; | ||||
| import envoy.client.net.Client; | ||||
| import envoy.client.net.MessageCache; | ||||
| import envoy.client.net.WriteProxy; | ||||
| import envoy.client.util.EnvoyLog; | ||||
| import envoy.data.LoginCredentials; | ||||
| import envoy.data.Message; | ||||
| import envoy.data.User; | ||||
| import envoy.exception.EnvoyException; | ||||
|  | ||||
| @@ -64,7 +62,7 @@ public class Startup { | ||||
| 			// Override configuration values with command line arguments | ||||
| 			if (args.length > 0) config.load(args); | ||||
|  | ||||
| 			// Check if all configuration values have been initialized | ||||
| 			// Check if all mandatory configuration values have been initialized | ||||
| 			if (!config.isInitialized()) throw new EnvoyException("Server or port are not defined"); | ||||
| 		} catch (Exception e) { | ||||
| 			JOptionPane | ||||
| @@ -77,8 +75,8 @@ public class Startup { | ||||
| 		EnvoyLog.setFileLevelBarrier(config.getFileLevelBarrier()); | ||||
| 		EnvoyLog.setConsoleLevelBarrier(config.getConsoleLevelBarrier()); | ||||
|  | ||||
| 		// Ask the user for their user name and password | ||||
| 		LoginCredentials credentials = new LoginDialog().getCredentials(); | ||||
| 		// Acquire login credentials | ||||
| 		LoginCredentials credentials = config.hasLoginCredentials() ? config.getLoginCredentials() : new LoginDialog().getCredentials(); | ||||
|  | ||||
| 		if (credentials == null) { | ||||
| 			logger.info("The login process has been aborted by the user. Exiting..."); | ||||
| @@ -108,11 +106,11 @@ public class Startup { | ||||
| 		// Acquire the client user (with ID) either from the server or from the local | ||||
| 		// database, which triggers offline mode | ||||
| 		Client			client	= new Client(); | ||||
| 		MessageCache	cache	= null; | ||||
| 		Cache<Message>	cache	= new Cache<>(); | ||||
| 		try { | ||||
| 			// Try entering online mode first | ||||
| 			localDb.loadIdGenerator(); | ||||
| 			cache = client.onlineInit(credentials, localDb); | ||||
| 			client.onlineInit(credentials, localDb, cache); | ||||
| 		} catch (Exception e1) { | ||||
| 			logger.warning("Could not connect to server. Trying offline mode..."); | ||||
| 			e1.printStackTrace(); | ||||
| @@ -139,7 +137,7 @@ public class Startup { | ||||
| 		// Initialize chats in local database | ||||
| 		try { | ||||
| 			localDb.initializeUserStorage(); | ||||
| 			localDb.loadChats(); | ||||
| 			localDb.loadUserData(); | ||||
| 		} catch (FileNotFoundException e) { | ||||
| 			// The local database file has not yet been created, probably first login | ||||
| 		} catch (Exception e) { | ||||
| @@ -150,14 +148,22 @@ public class Startup { | ||||
| 					JOptionPane.WARNING_MESSAGE); | ||||
| 		} | ||||
|  | ||||
| 		// Save all users to the local database | ||||
| 		if (client.isOnline()) localDb.setUsers(client.getUsers()); | ||||
| 		// Initialize write proxy | ||||
| 		final WriteProxy writeProxy = client.createWriteProxy(localDb); | ||||
|  | ||||
| 		// Save all users to the local database and flush cache | ||||
| 		if (client.isOnline()) { | ||||
| 			localDb.setUsers(client.getUsers()); | ||||
| 			writeProxy.flushCache(); | ||||
| 		} | ||||
|  | ||||
| 		// Display ChatWindow and StatusTrayIcon | ||||
| 		EventQueue.invokeLater(() -> { | ||||
| 			try { | ||||
| 				chatWindow.setClient(client); | ||||
| 				chatWindow.setLocalDB(localDb); | ||||
| 				chatWindow.initContent(client, localDb, writeProxy); | ||||
|  | ||||
| 				// Relay unread messages from cache | ||||
| 				if (cache != null) cache.relay(); | ||||
|  | ||||
| 				try { | ||||
| 					new StatusTrayIcon(chatWindow).show(); | ||||
| @@ -177,9 +183,6 @@ public class Startup { | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		// Relay unread messages from cache | ||||
| 		if (cache != null) cache.relayMessages(); | ||||
|  | ||||
| 		// Save Settings and PersistentLocalDb on shutdown | ||||
| 		Runtime.getRuntime().addShutdownHook(new Thread(() -> { | ||||
| 			try { | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.util.logging.*; | ||||
|  | ||||
| import envoy.client.Config; | ||||
| import envoy.client.data.Config; | ||||
|  | ||||
| /** | ||||
|  * Project: <strong>envoy-client</strong><br> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 GitHub
						GitHub