Added Client#sendMessage(Message), closing socket on exit

This commit is contained in:
Kai S. K. Engelbart 2019-12-31 11:57:11 +02:00
parent 22ccddcc39
commit 1fec53f35a
5 changed files with 81 additions and 44 deletions

View File

@ -1,11 +1,14 @@
package envoy.client; package envoy.client;
import java.io.Closeable;
import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
import envoy.client.util.EnvoyLog; import envoy.client.util.EnvoyLog;
import envoy.data.LoginCredentials; import envoy.data.LoginCredentials;
import envoy.data.Message;
import envoy.data.User; import envoy.data.User;
import envoy.exception.EnvoyException; import envoy.exception.EnvoyException;
import envoy.util.SerializationUtils; import envoy.util.SerializationUtils;
@ -20,13 +23,16 @@ import envoy.util.SerializationUtils;
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy v0.1-alpha * @since Envoy v0.1-alpha
*/ */
public class Client { public class Client implements Closeable {
private Socket socket;
private Receiver receiver;
private boolean online;
private Socket socket;
private Config config = Config.getInstance();
private volatile User sender; private volatile User sender;
private User recipient; private User recipient;
private boolean online;
private Config config = Config.getInstance();
private static final Logger logger = EnvoyLog.getLogger(Client.class.getSimpleName()); private static final Logger logger = EnvoyLog.getLogger(Client.class.getSimpleName());
@ -39,20 +45,21 @@ public class Client {
* @since Envoy v0.2-alpha * @since Envoy v0.2-alpha
*/ */
public void onlineInit(LoginCredentials credentials) throws Exception { public void onlineInit(LoginCredentials credentials) throws Exception {
// Establish TCP connection
logger.info(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort())); logger.info(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
socket = new Socket(config.getServer(), config.getPort()); socket = new Socket(config.getServer(), config.getPort());
logger.info("Successfully connected to server."); logger.info("Successfully connected to server.");
// Write login credentials
logger.finest("Sending login credentials...");
SerializationUtils.writeBytesWithLength(credentials, socket.getOutputStream());
// Create message receiver // Create message receiver
Receiver receiver = new Receiver(socket.getInputStream()); receiver = new Receiver(socket.getInputStream());
// Register user creation processor // Register user creation processor
receiver.registerProcessor(User.class, sender -> { logger.info("Acquired user object " + sender); this.sender = sender; }); receiver.registerProcessor(User.class, sender -> { logger.info("Acquired user object " + sender); this.sender = sender; });
// Write login credentials
logger.finest("Sending login credentials...");
SerializationUtils.writeBytesWithLength(credentials, socket.getOutputStream());
// Start receiver // Start receiver
new Thread(receiver).start(); new Thread(receiver).start();
@ -66,6 +73,18 @@ public class Client {
online = true; online = true;
} }
/**
* Sends a message to the server.
*
* @param message the message to send
* @throws IOException if the message does not reach the server
* @since Envoy v0.3-alpha
*/
public void sendMessage(Message message) throws IOException {
if (!online) throw new IllegalStateException("Client is not online");
SerializationUtils.writeBytesWithLength(message, socket.getOutputStream());
}
/** /**
* @return a {@code Map<String, User>} of all users on the server with their * @return a {@code Map<String, User>} of all users on the server with their
* user names as keys * user names as keys
@ -76,6 +95,9 @@ public class Client {
return null; return null;
} }
@Override
public void close() throws IOException { if (online) socket.close(); }
/** /**
* @return the sender object that represents this client. * @return the sender object that represents this client.
* @since Envoy v0.1-alpha * @since Envoy v0.1-alpha
@ -110,6 +132,11 @@ public class Client {
*/ */
public boolean hasRecipient() { return recipient != null; } public boolean hasRecipient() { return recipient != null; }
/**
* @return the {@link Receiver} used by this {@link Client}
*/
public Receiver getReceiver() { return receiver; }
/** /**
* @return {@code true} if a connection to the server could be established * @return {@code true} if a connection to the server could be established
* @since Envoy v0.2-alpha * @since Envoy v0.2-alpha

View File

@ -1,15 +0,0 @@
package envoy.client;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ObjectProcessor.java</strong><br>
* Created: <strong>30.12.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy v0.3-alpha
*/
public interface ObjectProcessor<T> {
void process(T input);
}

View File

@ -4,6 +4,7 @@ import java.io.InputStream;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -19,11 +20,16 @@ import envoy.client.util.EnvoyLog;
*/ */
public class Receiver implements Runnable { public class Receiver implements Runnable {
private InputStream in; private InputStream in;
private Map<Class<?>, ObjectProcessor<?>> processors = new HashMap<>(); private Map<Class<?>, Consumer<?>> processors = new HashMap<>();
private static final Logger logger = EnvoyLog.getLogger(Receiver.class.getSimpleName()); private static final Logger logger = EnvoyLog.getLogger(Receiver.class.getSimpleName());
/**
* Creates an instance of {@link Receiver}.
*
* @param in the {@link InputStream} to parse objects from
*/
public Receiver(InputStream in) { this.in = in; } public Receiver(InputStream in) { this.in = in; }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -35,16 +41,23 @@ public class Receiver implements Runnable {
logger.finest("Received object " + obj); logger.finest("Received object " + obj);
// Get appropriate processor // Get appropriate processor
ObjectProcessor processor = processors.get(obj.getClass()); @SuppressWarnings("rawtypes")
Consumer processor = processors.get(obj.getClass());
if (processor == null) if (processor == null)
logger.severe(String.format("The received object has the class %s for which no processor is defined.", obj.getClass())); logger.severe(String.format("The received object has the class %s for which no processor is defined.", obj.getClass()));
else else processor.accept(obj);
processor.process(obj);
} }
} catch(Exception e) { } catch (Exception e) {
logger.log(Level.SEVERE, "Error on receiver thread", e); logger.log(Level.SEVERE, "Error on receiver thread", e);
} }
} }
public <T> void registerProcessor(Class<T> processorClass, ObjectProcessor<T> processor) { processors.put(processorClass, processor); } /**
* Adds an object processor to this {@link Receiver}. It will be called once an
* object of the accepted class has been received.
*
* @param processorClass the object class accepted by the processor
* @param processor the object processor
*/
public <T> void registerProcessor(Class<T> processorClass, Consumer<T> processor) { processors.put(processorClass, processor); }
} }

View File

@ -246,8 +246,13 @@ public class ChatWindow extends JFrame {
if (!messageEnterTextArea.getText().isEmpty()) try { if (!messageEnterTextArea.getText().isEmpty()) try {
// Create and send message object // Create message
final Message message = new MessageBuilder(localDB.getUser(), currentChat.getRecipient()).setText(messageEnterTextArea.getText()).build(); final Message message = new MessageBuilder(localDB.getUser(), currentChat.getRecipient()).setText(messageEnterTextArea.getText()).build();
// Send message
client.sendMessage(message);
// Add message to LocalDB and update UI
currentChat.appendMessage(message); currentChat.appendMessage(message);
messageList.setModel(currentChat.getModel()); messageList.setModel(currentChat.getModel());

View File

@ -2,6 +2,7 @@ package envoy.client.ui;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -76,7 +77,7 @@ public class Startup {
logger.severe("User name is not set or empty. Exiting..."); logger.severe("User name is not set or empty. Exiting...");
System.exit(1); System.exit(1);
} }
// TODO: create dialog // TODO: create dialog
String pass = JOptionPane.showInputDialog("Enter password"); String pass = JOptionPane.showInputDialog("Enter password");
@ -125,17 +126,17 @@ public class Startup {
// Initialize chats in local database // Initialize chats in local database
try { try {
localDB.initializeDBFile(); localDB.initializeDBFile();
// TODO: localDB.loadChats(); localDB.loadChats();
} catch (FileNotFoundException e) {
// The local database file has not yet been created, probably first login
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
JOptionPane.showMessageDialog(null, JOptionPane.showMessageDialog(null,
"Error while loading local database: " + e.toString() + "\nChats will not be stored locally.", "Error while loading local database: " + e.toString() + "\nChats might not be stored locally.",
"Local DB error", "Local DB error",
JOptionPane.WARNING_MESSAGE); JOptionPane.WARNING_MESSAGE);
} }
logger.info("Client user ID: " + client.getSender().getId());
// Save all users to the local database // Save all users to the local database
if (client.isOnline()) localDB.setUsers(client.getUsers()); if (client.isOnline()) localDB.setUsers(client.getUsers());
@ -147,8 +148,13 @@ public class Startup {
try { try {
new StatusTrayIcon(chatWindow).show(); new StatusTrayIcon(chatWindow).show();
// If the tray icon is supported and corresponding settings is set, hide the chat window on close // If the tray icon is supported and corresponding settings is set, hide the
Settings.getInstance().getItems().get("onCloseMode").setChangeHandler((onCloseMode) -> chatWindow.setDefaultCloseOperation((boolean) onCloseMode ? JFrame.HIDE_ON_CLOSE : JFrame.EXIT_ON_CLOSE)); // chat window on close
Settings.getInstance()
.getItems()
.get("onCloseMode")
.setChangeHandler((onCloseMode) -> chatWindow
.setDefaultCloseOperation((boolean) onCloseMode ? JFrame.HIDE_ON_CLOSE : JFrame.EXIT_ON_CLOSE));
} catch (EnvoyException e) { } catch (EnvoyException e) {
logger.warning("The StatusTrayIcon is not supported on this platform!"); logger.warning("The StatusTrayIcon is not supported on this platform!");
} }
@ -160,13 +166,14 @@ public class Startup {
// Save Settings and LocalDB on shutdown // Save Settings and LocalDB on shutdown
Runtime.getRuntime().addShutdownHook(new Thread(() -> { Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try { try {
logger.info("Saving local database..."); logger.info("Closing connection...");
client.close();
logger.info("Saving local database and settings...");
localDB.save(); localDB.save();
logger.info("Saving settings...");
Settings.getInstance().save(); Settings.getInstance().save();
} catch (IOException e1) { } catch (IOException e) {
e1.printStackTrace(); logger.log(Level.SEVERE, "Unable to save local files", e);
logger.log(Level.WARNING, "Unable to save the messages", e1);
} }
})); }));
} }