From d09432cecf90cdd81d233e05f642ac3d249b1532 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Wed, 27 Nov 2019 06:30:05 +0100 Subject: [PATCH] Envoy Client v0.1-alpha --- .classpath | 32 ++ .gitignore | 2 + .project | 40 ++ .settings/org.eclipse.core.resources.prefs | 6 + .settings/org.eclipse.jdt.core.prefs | 8 + .settings/org.eclipse.m2e.core.prefs | 4 + pom.xml | 41 ++ src/main/java/envoy/client/Chat.java | 45 +++ src/main/java/envoy/client/Client.java | 179 +++++++++ src/main/java/envoy/client/Config.java | 127 +++++++ src/main/java/envoy/client/LocalDB.java | 293 +++++++++++++++ src/main/java/envoy/client/ui/ChatWindow.java | 349 ++++++++++++++++++ .../envoy/client/ui/MessageListRenderer.java | 54 +++ .../java/envoy/client/ui/SettingsScreen.java | 156 ++++++++ src/main/java/envoy/client/ui/Startup.java | 73 ++++ .../envoy/client/ui/UserListRenderer.java | 65 ++++ src/main/resources/client.properties | 3 + 17 files changed, 1477 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 .settings/org.eclipse.m2e.core.prefs create mode 100644 pom.xml create mode 100644 src/main/java/envoy/client/Chat.java create mode 100644 src/main/java/envoy/client/Client.java create mode 100644 src/main/java/envoy/client/Config.java create mode 100644 src/main/java/envoy/client/LocalDB.java create mode 100644 src/main/java/envoy/client/ui/ChatWindow.java create mode 100644 src/main/java/envoy/client/ui/MessageListRenderer.java create mode 100644 src/main/java/envoy/client/ui/SettingsScreen.java create mode 100644 src/main/java/envoy/client/ui/Startup.java create mode 100644 src/main/java/envoy/client/ui/UserListRenderer.java create mode 100644 src/main/resources/client.properties diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..906bfce --- /dev/null +++ b/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e12b13a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target/ +/localDB/ \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000..08d7599 --- /dev/null +++ b/.project @@ -0,0 +1,40 @@ + + + envoy-client + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.jboss.tools.jst.web.kb.kbbuilder + + + + + org.jboss.tools.cdi.core.cdibuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.jboss.tools.jst.web.kb.kbnature + org.jboss.tools.cdi.core.cdinature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..04cfa2c --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..cb635b1 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..14b697b --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d688c37 --- /dev/null +++ b/pom.xml @@ -0,0 +1,41 @@ + + 4.0.0 + + informatik-ag-ngl + envoy-client + 0.0.1-SNAPSHOT + + Envoy Client + https://github.com/informatik-ag-ngl/envoy-client + + + UTF-8 + UTF-8 + 1.8 + 1.8 + + + + + org.jboss.resteasy + resteasy-client + 4.1.1.Final + + + org.jboss.resteasy + resteasy-jaxb-provider + 4.3.1.Final + + + informatik-ag-ngl + envoy-common + 0.0.1-SNAPSHOT + + + + + envoy-client + + \ No newline at end of file diff --git a/src/main/java/envoy/client/Chat.java b/src/main/java/envoy/client/Chat.java new file mode 100644 index 0000000..da35bd3 --- /dev/null +++ b/src/main/java/envoy/client/Chat.java @@ -0,0 +1,45 @@ +package envoy.client; + +import java.io.Serializable; + +import javax.swing.DefaultListModel; + +import envoy.schema.Message; +import envoy.schema.User; + +public class Chat implements Serializable { + + private static final long serialVersionUID = -7751248474547242056L; + + private User recipient; + private DefaultListModel model = new DefaultListModel<>(); + + /** + * Provides the list of messages that the recipient receives.
+ * Saves the Messages in the corresponding chat at that Point. + * + * @param recipient the user who receives the messages + * @since Envoy v0.1-alpha + */ + public Chat(User recipient) { this.recipient = recipient; } + + /** + * @return the recipient of a message + * @since Envoy v0.1-alpha + */ + public User getRecipient() { return recipient; } + + /** + * Adds the received message at the current Point in the current chat + * + * @param message the message to add in said chat + * @since Envoy v0.1-alpha + */ + public void appendMessage(Message message) { model.addElement(message); } + + /** + * @return all messages in the current chat + * @since Envoy v0.1-alpha + */ + public DefaultListModel getModel() { return model; } +} \ No newline at end of file diff --git a/src/main/java/envoy/client/Client.java b/src/main/java/envoy/client/Client.java new file mode 100644 index 0000000..522d44f --- /dev/null +++ b/src/main/java/envoy/client/Client.java @@ -0,0 +1,179 @@ +package envoy.client; + +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Response; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; + +import envoy.schema.ObjectFactory; +import envoy.schema.Sync; +import envoy.schema.User; + +/** + * Project: envoy-client
+ * File: Client.java
+ * Created: 28 Sep 2019
+ * + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @author Leon Hofmeister + * @since Envoy v0.1-alpha + */ +public class Client { + + private ObjectFactory objectFactory = new ObjectFactory(); + private Config config; + private User sender, recipient; + + public Client(Config config, String username) { + this.config = config; + sender = getUser(username); + System.out.println("ID: " + sender.getID()); + } + + private R post(String uri, T body, Class responseBodyClass) { + javax.ws.rs.client.Client client = ClientBuilder.newClient(); + WebTarget target = client.target(uri); + + Response response = target.request().post(Entity.entity(body, "application/xml")); + R responseBody = response.readEntity(responseBodyClass); + response.close(); + client.close(); + + return responseBody; + + } + + /** + * Returns a {@link Sync} with all users on the server. + * + * @return Sync - List of all users on the server. + * @since Envoy v0.1-alpha + */ + public Sync getUsersListXml() { + Sync sendSync = objectFactory.createSync(); + User user = objectFactory.createUser(); + user.setID(-1); + sendSync.getUsers().add(user); + + Sync returnSendSync = post( + String + .format("%s:%d/envoy-server/rest/sync/syncData?userId=%d", config.getServer(), config.getPort(), 0), + sendSync, + Sync.class); + return returnSendSync; + + } + + /** + * Returns a {@link User} with a specific id by name. + * + * @param name - the name of the {@link User} + * @return a {@link User} with the specified name + * @since Envoy v0.1-alpha + */ + private User getUser(String name) { + Sync senderSync = objectFactory.createSync(); + User user = objectFactory.createUser(); + user.setName(name); + senderSync.getUsers().add(user); + + Sync returnSenderSync = post( + String + .format("%s:%d/envoy-server/rest/sync/syncData?userId=%d", config.getServer(), config.getPort(), 0), + senderSync, + Sync.class); + + User returnSender = objectFactory.createUser(); + + if (returnSenderSync.getUsers().size() == 1) { + returnSender = returnSenderSync.getUsers().get(0); + } else { + System.out.println("ERROR exiting..."); + } + + return returnSender; + } + + /** + * Sends the "sync" Sync Object to the server and gets a "returnSync" Sync + * Object as response.
+ * It is also used to get the own sender at the start of the client + * (Client sends "sync" Sync Object with single user in it(name: the name + * entered at login, id: 0, UserStatus:null))
+ * and to get a complete list of all users saved on the server. + * (Client sends "sync" Sync Object with single user in it(name: "" (empty), id: + * -1, UserStatus:null))
+ * This method also processes the response Sync Object.
+ * It sorts its users and messages by specific variables and does certain things + * with them.
+ *
+ * Messages:
+ * -State SENT: Update Local message(s) with State WAITING (add Message ID and + * change State to SENT). (server sends these informations to the client if + * message(s) with State WAITING were successfully sent to the server)
+ * -State RECEIVED, SenderID != 0: Adds the unread Messages returned from the + * server in the latest sync to the "unreadMessagesSync" Sync Object.
+ * -State RECEIVED, SenderID == 0: Update message(s) in localDB to state + * RECEIVED. + * (server sends these informations to the client if the other client received + * the message(s).)
+ * -State READ: Update message(s) in the LocalDB to state READ. (server sends + * these informations to the client if the other client read + * the message(s).)
+ *
+ * Users:
+ * Updating UserStatus of all users in LocalDB. (Server sends all users with + * their updated UserStatus to the client.)
+ * + * @param userId + * @since Envoy v0.1-alpha + */ + public Sync sendSync(long userId, Sync sync) { + // Print sync XML to console + JAXBContext jc; + try { + jc = JAXBContext.newInstance("envoy.schema"); + Marshaller m = jc.createMarshaller(); + m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + m.marshal(sync, System.out); + } catch (JAXBException e) { + e.printStackTrace(); + } + + // Send sync + return post(String + .format("%s:%d/envoy-server/rest/sync/syncData?userId=%d", config.getServer(), config.getPort(), userId), + sync, + Sync.class); + } + + /** + * @return the sender object that represents this client. + * @since Envoy v0.1-alpha + */ + public User getSender() { return sender; } + + /** + * @return the current recipient of the current chat. + * @since Envoy v0.1-alpha + */ + public User getRecipient() { return recipient; } + + /** + * Sets the recipient. + * @param recipient - the recipient to set + * @since Envoy v0.1-alpha + */ + public void setRecipient(User recipient) { this.recipient = recipient; } + + /** + * @return true, if a recipient is selected + * @since Envoy v0.1-alpha + */ + public boolean hasRecipient() { return recipient != null; } +} + diff --git a/src/main/java/envoy/client/Config.java b/src/main/java/envoy/client/Config.java new file mode 100644 index 0000000..c31c972 --- /dev/null +++ b/src/main/java/envoy/client/Config.java @@ -0,0 +1,127 @@ +package envoy.client; + +import java.io.File; +import java.util.Properties; + +/** + * Project: envoy-client
+ * File: Config.java
+ * Created: 12 Oct 2019
+ * + * @author Kai S. K. Engelbart + * @since Envoy v0.1-alpha + */ +public class Config { + + private String server; + private int port; + private File localDB; + private int syncTimeout; + + private static Config config; + + private Config() {} + + public static Config getInstance() { + if (config == null) config = new Config(); + return config; + } + + /** + * Defaults to the {@code client.properties} file for information. + * + * @param properties a {@link Properties} object containing information about + * the server and port, as well as the path to the local + * database + * @since Envoy v0.1-alpha + */ + public void load(Properties properties) { + if (properties.containsKey("server")) server = properties.getProperty("server"); + if (properties.containsKey("port")) port = Integer.parseInt(properties.getProperty("port")); + localDB = new File(properties.getProperty("localDB", ".\\localDB")); + syncTimeout = Integer.parseInt(properties.getProperty("syncTimeout", "1000")); + } + + /** + * Sets the server, port and localDB path via command line properties --server / + * -s, --port / -p and --localDB / -db. + * + * @param args the command line arguments to parse + * @since Envoy v0.1-alpha + */ + public void load(String[] args) { + for (int i = 0; i < args.length; i++) + switch (args[i]) { + case "--server": + case "-s": + server = args[++i]; + break; + case "--port": + case "-p": + port = Integer.parseInt(args[++i]); + break; + case "--localDB": + case "-db": + localDB = new File(args[++i]); + } + if (localDB == null) localDB = new File(".\\localDB"); + if (syncTimeout == 0) syncTimeout = 1000; + } + + /** + * @return {@code true} if server, port and localDB directory are known. + * @since Envoy v0.1-alpha + */ + public boolean isInitialized() { + return server != null && !server.isEmpty() && port > 0 && port < 65566 && localDB != null; + } + + /** + * @return the host name of the Envoy server + * @since Envoy v0.1-alpha + */ + public String getServer() { return server; } + + /** + * Changes the default server host name. + * Exclusively intended for development purposes. + * + * @param server the host name of the Envoy server + * @since Envoy v0.1-alpha + */ + public void setServer(String server) { this.server = server; } + + /** + * @return the port at which the Envoy server is located on the host + * @since Envoy v0.1-alpha + */ + public int getPort() { return port; } + + /** + * Changes the default port. + * Exclusively intended for development purposes. + * + * @param port the port where an Envoy server is located + * @since Envoy v0.1-alpha + */ + public void setPort(int port) { this.port = port; } + + /** + * @return the local database specific to the client user + * @since Envoy v0.1-alpha + **/ + public File getLocalDB() { return localDB; } + + /** + * Changes the default local database. + * Exclusively intended for development purposes. + * + * @param the file containing the local database + * @since Envoy v0.1-alpha + **/ + public void setLocalDB(File localDB) { this.localDB = localDB; } + + public int getSyncTimeout() { return syncTimeout; } + + public void setSyncTimeout(int syncTimeout) { this.syncTimeout = syncTimeout; } +} diff --git a/src/main/java/envoy/client/LocalDB.java b/src/main/java/envoy/client/LocalDB.java new file mode 100644 index 0000000..3cc96a2 --- /dev/null +++ b/src/main/java/envoy/client/LocalDB.java @@ -0,0 +1,293 @@ +package envoy.client; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; + +import envoy.exception.EnvoyException; +import envoy.schema.Message; +import envoy.schema.Message.Metadata.MessageState; +import envoy.schema.ObjectFactory; +import envoy.schema.Sync; +import envoy.schema.User; + +/** + * Project: envoy-client
+ * File: LocalDB.java
+ * Created: 27.10.2019
+ * + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @since Envoy v0.1-alpha + */ +public class LocalDB { + + private File localDB; + private User sender; + private List chats = new ArrayList<>(); + private ObjectFactory objectFactory = new ObjectFactory(); + private DatatypeFactory datatypeFactory; + + private Sync unreadMessagesSync = objectFactory.createSync(); + private Sync sync = objectFactory.createSync(); + private Sync readMessages = objectFactory.createSync(); + + /** + * Constructs an empty local database. + * + * @param client the user that is logged in with this client + * @since Envoy v0.1-alpha + */ + + public LocalDB(User sender) { + this.sender = sender; + try { + datatypeFactory = DatatypeFactory.newInstance(); + } catch (DatatypeConfigurationException e) { + e.printStackTrace(); + } + } + + /** + * Initializes the local database and fills it with values + * if the user has already sent or received messages. + * + * @param localDBDir the directory where we wish to save/load the database from. + * @throws EnvoyException if the directory selected is not an actual directory. + * @since Envoy v0.1-alpha + */ + public void initializeDBFile(File localDBDir) throws EnvoyException { + if (localDBDir.exists() && !localDBDir.isDirectory()) + throw new EnvoyException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath())); + localDB = new File(localDBDir, sender.getID() + ".db"); + if (localDB.exists()) loadFromLocalDB(); + } + + /** + * Saves the database into a file for future use. + * + * @throws IOException if something went wrong during saving + * @since Envoy v0.1-alpha + */ + public void saveToLocalDB() { + try { + localDB.getParentFile().mkdirs(); + localDB.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + System.err.println("unable to save the messages"); + } + try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) { + out.writeObject(chats); + } catch (IOException ex) { + ex.printStackTrace(); + System.err.println("unable to save the messages"); + } + } + + /** + * Loads all chats saved by Envoy for the client user. + * + * @throws EnvoyException if something fails while loading. + * @since Envoy v0.1-alpha + */ + @SuppressWarnings("unchecked") + private void loadFromLocalDB() throws EnvoyException { + try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(localDB))) { + Object obj = in.readObject(); + if (obj instanceof ArrayList) chats = (ArrayList) obj; + } catch (ClassNotFoundException | IOException e) { + throw new EnvoyException(e); + } + } + + /** + * Creates a {@link Message} object serializable to XML. + * + * @param textContent The content (text) of the message + * @return prepared {@link Message} object + */ + public Message createMessage(String textContent, User recipient) { + Message.Metadata metaData = objectFactory.createMessageMetadata(); + metaData.setSender(sender.getID()); + metaData.setRecipient(recipient.getID()); + metaData.setState(MessageState.WAITING); + metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString())); + + Message.Content content = objectFactory.createMessageContent(); + content.setType("text"); + content.setText(textContent); + + Message message = objectFactory.createMessage(); + message.setMetadata(metaData); + message.getContent().add(content); + + return message; + } + + public Sync fillSync(long userId) { + addWaitingMessagesToSync(); + + sync.getMessages().addAll(readMessages.getMessages()); + readMessages.getMessages().clear(); + + System.out.println(sync.getMessages().size()); + return sync; + } + + public void applySync(Sync returnSync) { + for (int i = 0; i < returnSync.getMessages().size(); i++) { + if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 + && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.SENT) { + // Update Local Messages with State WAITING (add Message ID and change State to + // SENT) + for (int j = 0; j < sync.getMessages().size(); j++) { + if (j == i) { + sync.getMessages().get(j).getMetadata().setMessageId(returnSync.getMessages().get(j).getMetadata().getMessageId()); + sync.getMessages().get(j).getMetadata().setState(returnSync.getMessages().get(j).getMetadata().getState()); + } + } + } + if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() != 0 + && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) { + // these are the unread Messages from the server + unreadMessagesSync.getMessages().add(returnSync.getMessages().get(i)); + } + + if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() == 0 + && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) { + // Update Messages in localDB to state RECEIVED + for (int j = 0; j < getChats().size(); j++) { + if (getChats().get(j).getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) { + for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) { + if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages() + .get(i) + .getMetadata() + .getMessageId()) { + // Update Message in LocalDB + getChats().get(j).getModel().get(k).getMetadata().setState(returnSync.getMessages().get(j).getMetadata().getState()); + } + } + } + } + + } + + if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 + && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.READ) { + // Update local Messages to state READ + System.out.println("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId() + + "was initialized to be set to READ in localDB."); + for (int j = 0; j < getChats().size(); j++) { + if (getChats().get(j).getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) { + System.out.println("Chat with: " + getChats().get(j).getRecipient().getID() + "was selected."); + for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) { + if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages() + .get(i) + .getMetadata() + .getMessageId()) { + System.out.println( + "Message with ID: " + getChats().get(j).getModel().get(k).getMetadata().getMessageId() + "was selected."); + getChats().get(j).getModel().get(k).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState()); + System.out + .println("Message State is now: " + getChats().get(j).getModel().get(k).getMetadata().getState().toString()); + } + } + } + } + } + } + + // Updating UserStatus of all users in LocalDB + for (int j = 0; j < returnSync.getUsers().size(); j++) { + for (int k = 0; k < getChats().size(); k++) { + if (getChats().get(k).getRecipient().getID() == returnSync.getUsers().get(j).getID()) { + + getChats().get(k).getRecipient().setStatus(returnSync.getUsers().get(j).getStatus()); + System.out.println(getChats().get(k).getRecipient().getStatus().toString()); + } + } + } + + sync.getMessages().clear(); + sync.getUsers().clear(); + } + + /** + * Adds the unread messages returned from the server in the latest sync to the + * right chats in the LocalDB. + * + * @param localDB + * @since Envoy v0.1-alpha + */ + public void addUnreadMessagesToLocalDB() { + Sync unreadMessages = unreadMessagesSync; + for (int i = 0; i < unreadMessages.getMessages().size(); i++) + for (int j = 0; j < getChats().size(); j++) + if (getChats().get(j).getRecipient().getID() == unreadMessages.getMessages().get(i).getMetadata().getSender()) { + getChats().get(j).appendMessage(unreadMessages.getMessages().get(i)); + } + } + + /** + * Changes all messages with state {@code RECEIVED} of a specific chat to state + * {@code READ}. + *
+ * Adds these messages to the {@code readMessages} {@link Sync} object. + * + * @param currentChat + * @since Envoy v0.1-alpha + */ + public void setMessagesToRead(Chat currentChat) { + for (int i = currentChat.getModel().size() - 1; i >= 0; --i) + if (currentChat.getModel().get(i).getMetadata().getRecipient() != currentChat.getRecipient().getID()) + if (currentChat.getModel().get(i).getMetadata().getState() == MessageState.RECEIVED) { + currentChat.getModel().get(i).getMetadata().setState(MessageState.READ); + readMessages.getMessages().add(currentChat.getModel().get(i)); + } else break; + } + + /** + * Adds all messages with state {@code WAITING} from the {@link LocalDB} to the + * {@link Sync} object. + * + * @since Envoy v0.1-alpha + */ + private void addWaitingMessagesToSync() { + for (Chat chat : getChats()) + for (int i = 0; i < chat.getModel().size(); i++) + if (chat.getModel().get(i).getMetadata().getState() == MessageState.WAITING) { + System.out.println("Got Waiting Message"); + sync.getMessages().add(chat.getModel().get(i)); + } + } + + /** + * Clears the {@code unreadMessagesSync} {@link Sync} object. + * + * @since Envoy v0.1-alpha + */ + public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); } + + /** + * @return all saves {@link Chat} objects that list the client user as the + * client + * @since Envoy v0.1-alpha + **/ + public List getChats() { return chats; } + + /** + * @return the {@link User} who initialized the local database + * @since Envoy v0.1-alpha + */ + public User getUser() { return sender; } +} diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java new file mode 100644 index 0000000..32b024b --- /dev/null +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -0,0 +1,349 @@ +package envoy.client.ui; + +import java.awt.Color; +import java.awt.ComponentOrientation; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextPane; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.border.EmptyBorder; + +import envoy.client.Chat; +import envoy.client.Client; +import envoy.client.Config; +import envoy.client.LocalDB; +import envoy.schema.Message; +import envoy.schema.Sync; +import envoy.schema.User; + +/** + * Project: envoy-client
+ * File: ChatWindow.java
+ * Created: 28 Sep 2019
+ * + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @author Leon Hofmeister + * @since Envoy v0.1-alpha + */ +public class ChatWindow extends JFrame { + + private static final long serialVersionUID = 6865098428255463649L; + + private JPanel contentPane = new JPanel(); + + private Client client; + private LocalDB localDB; + + private JList userList = new JList<>(); + private Chat currentChat; + + private JTextArea messageEnterTextArea; + + public ChatWindow(Client client, LocalDB localDB) { + this.client = client; + this.localDB = localDB; + + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setBounds(100, 100, 600, 800); + setTitle("Envoy"); + setLocationRelativeTo(null); + + // Save chats when window closes + addWindowListener(new WindowAdapter() { + + @Override + public void windowClosing(WindowEvent e) { localDB.saveToLocalDB(); } + }); + + contentPane.setBackground(new Color(0, 0, 0)); + contentPane.setForeground(Color.white); + contentPane.setBorder(new EmptyBorder(0, 5, 0, 0)); + setContentPane(contentPane); + GridBagLayout gbl_contentPane = new GridBagLayout(); + gbl_contentPane.columnWidths = new int[] { 1, 1, 1 }; + gbl_contentPane.rowHeights = new int[] { 1, 1, 1 }; + gbl_contentPane.columnWeights = new double[] { 0.3, 1.0, 0.1 }; + gbl_contentPane.rowWeights = new double[] { 0.05, 1.0, 0.07 }; + contentPane.setLayout(gbl_contentPane); + + JList messageList = new JList<>(); + messageList.setCellRenderer(new MessageListRenderer()); + + messageList.setFocusTraversalKeysEnabled(false); + messageList.setSelectionForeground(new Color(255, 255, 255)); + messageList.setSelectionBackground(new Color(102, 0, 153)); + messageList.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); + messageList.setForeground(new Color(255, 255, 255)); + messageList.setBackground(new Color(51, 51, 51)); + + DefaultListModel messageListModel = new DefaultListModel<>(); + messageList.setModel(messageListModel); + messageList.setFont(new Font("Arial", Font.PLAIN, 17)); + messageList.setFixedCellHeight(60); + messageList.setBorder(new EmptyBorder(5, 5, 5, 5)); + + JScrollPane scrollPane = new JScrollPane(); + scrollPane.setForeground(new Color(0, 0, 0)); + scrollPane.setBackground(new Color(51, 51, 51)); + scrollPane.setViewportView(messageList); + scrollPane.setBorder(null); + + GridBagConstraints gbc_scrollPane = new GridBagConstraints(); + gbc_scrollPane.fill = GridBagConstraints.BOTH; + gbc_scrollPane.gridwidth = 2; + gbc_scrollPane.gridx = 1; + gbc_scrollPane.gridy = 1; + + gbc_scrollPane.insets = new Insets(0, 10, 10, 10); + + contentPane.add(scrollPane, gbc_scrollPane); + + // Message enter field + messageEnterTextArea = new JTextArea(); + messageEnterTextArea.addKeyListener(new KeyAdapter() { + + @Override + public void keyReleased(KeyEvent e) { + + if (e.getKeyCode() == KeyEvent.VK_ENTER && ((SettingsScreen.enterToSend && e.getModifiersEx() == 0) + || (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) { + + postMessage(messageList); + } + + } + }); + // Checks for changed Message + messageEnterTextArea.setWrapStyleWord(true); + messageEnterTextArea.setCaretColor(new Color(255, 255, 255)); + messageEnterTextArea.setForeground(new Color(255, 255, 255)); + messageEnterTextArea.setBackground(new Color(51, 51, 51)); + messageEnterTextArea.setLineWrap(true); + messageEnterTextArea.setBorder(null); + messageEnterTextArea.setFont(new Font("Arial", Font.PLAIN, 17)); + messageEnterTextArea.setBorder(new EmptyBorder(5, 5, 5, 5)); + + GridBagConstraints gbc_messageEnterTextfield = new GridBagConstraints(); + gbc_messageEnterTextfield.fill = GridBagConstraints.BOTH; + gbc_messageEnterTextfield.gridx = 1; + gbc_messageEnterTextfield.gridy = 2; + + gbc_messageEnterTextfield.insets = new Insets(10, 10, 10, 10); + + contentPane.add(messageEnterTextArea, gbc_messageEnterTextfield); + + // Post Button + JButton postButton = new JButton("Post"); + postButton.setForeground(new Color(255, 255, 255)); + postButton.setBackground(new Color(102, 51, 153)); + postButton.setBorderPainted(false); + + GridBagConstraints gbc_moveSelectionPostButton = new GridBagConstraints(); + + gbc_moveSelectionPostButton.fill = GridBagConstraints.BOTH; + gbc_moveSelectionPostButton.gridx = 2; + gbc_moveSelectionPostButton.gridy = 2; + + gbc_moveSelectionPostButton.insets = new Insets(10, 10, 10, 10); + + postButton.addActionListener((evt) -> { postMessage(messageList); }); + contentPane.add(postButton, gbc_moveSelectionPostButton); + + // Settings Button + JButton settingsButton = new JButton("Settings"); + settingsButton.setForeground(new Color(255, 255, 255)); + settingsButton.setBackground(new Color(102, 51, 153)); + settingsButton.setBorderPainted(false); + + GridBagConstraints gbc_moveSelectionSettingsButton = new GridBagConstraints(); + + gbc_moveSelectionSettingsButton.fill = GridBagConstraints.BOTH; + gbc_moveSelectionSettingsButton.gridx = 2; + gbc_moveSelectionSettingsButton.gridy = 0; + + gbc_moveSelectionSettingsButton.insets = new Insets(10, 10, 10, 10); + + settingsButton.addActionListener((evt) -> { + try { + SettingsScreen.open(localDB.getUser().getName()); + } catch (Exception e) { + SettingsScreen.open(); + System.err.println("An error occured while opening the settings screen: " + e); + e.printStackTrace(); + } + }); + contentPane.add(settingsButton, gbc_moveSelectionSettingsButton); + + // Partner name display + JTextPane textPane = new JTextPane(); + textPane.setBackground(new Color(0, 0, 0)); + textPane.setForeground(new Color(255, 255, 255)); + + textPane.setFont(new Font("Arial", Font.PLAIN, 20)); + + GridBagConstraints gbc_partnerName = new GridBagConstraints(); + gbc_partnerName.fill = GridBagConstraints.HORIZONTAL; + gbc_partnerName.gridx = 1; + gbc_partnerName.gridy = 0; + + gbc_partnerName.insets = new Insets(0, 10, 0, 10); + contentPane.add(textPane, gbc_partnerName); + + userList.setCellRenderer(new UserListRenderer()); + userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + userList.addListSelectionListener((listSelectionEvent) -> { + if (!listSelectionEvent.getValueIsAdjusting()) { + @SuppressWarnings("unchecked") + final JList selectedUserList = (JList) listSelectionEvent.getSource(); + final User user = selectedUserList.getSelectedValue(); + client.setRecipient(user); + + currentChat = localDB.getChats() + .stream() + .filter(chat -> chat.getRecipient().getID() == user.getID()) + .findFirst() + .get(); + + // Set all unread messages in the chat to read + readCurrentChat(); + + client.setRecipient(user); + + textPane.setText(currentChat.getRecipient().getName()); + + messageList.setModel(currentChat.getModel()); + contentPane.revalidate(); + } + }); + + userList.setSelectionForeground(new Color(255, 255, 255)); + userList.setSelectionBackground(new Color(102, 0, 153)); + userList.setForeground(new Color(255, 255, 255)); + userList.setBackground(new Color(51, 51, 51)); + userList.setFont(new Font("Arial", Font.PLAIN, 17)); + userList.setBorder(new EmptyBorder(5, 5, 5, 5)); + + GridBagConstraints gbc_userList = new GridBagConstraints(); + gbc_userList.fill = GridBagConstraints.VERTICAL; + gbc_userList.gridx = 0; + gbc_userList.gridy = 1; + gbc_userList.anchor = GridBagConstraints.PAGE_START; + gbc_userList.insets = new Insets(0, 0, 10, 0); + + contentPane.add(userList, gbc_userList); + contentPane.revalidate(); + + loadUsersAndChats(); + startSyncThread(Config.getInstance().getSyncTimeout()); + + contentPane.revalidate(); + } + + private void postMessage(JList messageList) { + if (!client.hasRecipient()) { + JOptionPane.showMessageDialog(this, + "Please select a recipient!", + "Cannot send message", + JOptionPane.INFORMATION_MESSAGE); + } + + if (!messageEnterTextArea.getText().isEmpty()) try { + + // Create and send message object + final Message message = localDB.createMessage(messageEnterTextArea.getText(), currentChat.getRecipient()); + currentChat.appendMessage(message); + messageList.setModel(currentChat.getModel()); + + // Clear text field + messageEnterTextArea.setText(""); + contentPane.revalidate(); + } catch (Exception e) { + JOptionPane.showMessageDialog(this, + "An exception occured while sending a message. See the log for more details.", + "Exception occured", + JOptionPane.ERROR_MESSAGE); + e.printStackTrace(); + } + } + + /** + * Initializes the elements of the user list by downloading them from the + * server. + * + * @since Envoy v0.1-alpha + */ + private void loadUsersAndChats() { + new Thread(() -> { + Sync users = client.getUsersListXml(); + DefaultListModel userListModel = new DefaultListModel<>(); + users.getUsers().forEach(user -> { + userListModel.addElement(user); + + // Check if user exists in local DB + if (localDB.getChats().stream().filter(c -> c.getRecipient().getID() == user.getID()).count() == 0) + localDB.getChats().add(new Chat(user)); + }); + SwingUtilities.invokeLater(() -> userList.setModel(userListModel)); + }).start(); + } + + /** + * Updates the data model and the UI repeatedly after a certain amount of + * time. + * + * @param timeout the amount of time that passes between two requests sent to + * the server + * @since Envoy v0.1-alpha + */ + private void startSyncThread(int timeout) { + new Timer(timeout, (evt) -> { + new Thread(() -> { + + // Synchronize + localDB.applySync( + client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID()))); + + // Process unread messages + localDB.addUnreadMessagesToLocalDB(); + localDB.clearUnreadMessagesSync(); + + // Mark unread messages as read when they are in the current chat + readCurrentChat(); + + // Update UI + SwingUtilities + .invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); }); + }).start(); + }).start(); + } + + private void updateUserStates() { + for (int i = 0; i < userList.getModel().getSize(); i++) + for (int j = 0; j < localDB.getChats().size(); j++) + if (userList.getModel().getElementAt(i).getID() == localDB.getChats().get(j).getRecipient().getID()) + userList.getModel().getElementAt(i).setStatus(localDB.getChats().get(j).getRecipient().getStatus()); + } + + /** + * Marks messages in the current chat as {@code READ}. + */ + private void readCurrentChat() { if (currentChat != null) { localDB.setMessagesToRead(currentChat); } } +} diff --git a/src/main/java/envoy/client/ui/MessageListRenderer.java b/src/main/java/envoy/client/ui/MessageListRenderer.java new file mode 100644 index 0000000..9053c6c --- /dev/null +++ b/src/main/java/envoy/client/ui/MessageListRenderer.java @@ -0,0 +1,54 @@ +package envoy.client.ui; + +import java.awt.Component; +import java.text.SimpleDateFormat; + +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; + +import envoy.schema.Message; + +/** + * Defines how a message is displayed.
+ *
+ * + * Project: envoy-client
+ * File: UserListRenderer.java
+ * Created: 19 Oct 2019
+ * + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @since Envoy v0.1-alpha + */ +public class MessageListRenderer extends JLabel implements ListCellRenderer { + + private static final long serialVersionUID = 5164417379767181198L; + + @Override + public Component getListCellRendererComponent(JList list, Message value, int index, + boolean isSelected, boolean cellHasFocus) { + if (isSelected) { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + + setOpaque(true); + + final String text = value.getContent().get(0).getText(); + final String state = value.getMetadata().getState().toString(); + final String date = value.getMetadata().getDate() == null ? "" + : new SimpleDateFormat("dd.MM.yyyy HH:mm ") + .format(value.getMetadata().getDate().toGregorianCalendar().getTime()); + + setText(String.format( + "

%s

%s :%s", + date, + text, + state)); + return this; + } +} \ No newline at end of file diff --git a/src/main/java/envoy/client/ui/SettingsScreen.java b/src/main/java/envoy/client/ui/SettingsScreen.java new file mode 100644 index 0000000..7d5de28 --- /dev/null +++ b/src/main/java/envoy/client/ui/SettingsScreen.java @@ -0,0 +1,156 @@ +package envoy.client.ui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.FlowLayout; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; + +/** + * Project: envoy-client
+ * File: SettingsScreen.java
+ * Created: 31 Oct 2019
+ * + * @author Leon Hofmeister + */ +public class SettingsScreen extends JDialog { + + private static final long serialVersionUID = -4476913491263077107L; + private final JPanel contentPanel = new JPanel(); + public static boolean enterToSend = true; + + // TODO: Add a JPanel with all the Information necessary: + // change (Picture,Username, Email, Password) and toggle(light/dark mode, + // "ctrl+enter"/"enter" + // to send a message directly) + /** + * Open the Settings screen. + * Only suited for Dev/Error mode. + * Avoid usage. + * + * @since Envoy v0.1-alpha + */ + public static void open() { open(new SettingsScreen()); } + + /** + * Opens the Settings screen.
+ * Use preferably since everyone is already initialised.
+ * It personalises the screen more. + * + * @param username The name of the User + * @param Email The Email that is associated with that Account + * @since Envoy v0.1-alpha + */ + public static void open(String username) {// , String Email) {AUSKLAMMERN, WENN ANMELDUNG PER + // EMAIL IMPLEMENTIERT IST! + open(new SettingsScreen(username)); + } + + public static void open(SettingsScreen dialog) { + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + dialog.setModal(true); + dialog.setVisible(true); + } + + /** + * Builds the Settings screen.
+ * Use only as Dev/Error Mode.
+ * Avoid usage. + * + * @since Envoy v0.1-alpha + */ + public SettingsScreen() { + setBackground(Color.BLACK); + setBounds(100, 100, 450, 300); + getContentPane().setLayout(new BorderLayout()); + contentPanel.setBackground(Color.BLACK); + contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); + getContentPane().add(contentPanel, BorderLayout.CENTER); + contentPanel.setLayout(new BorderLayout(0, 0)); + { + JPanel buttonPane = new JPanel(); + buttonPane.setBackground(Color.BLACK); + getContentPane().add(buttonPane, BorderLayout.SOUTH); + buttonPane.setLayout(new BorderLayout(0, 0)); + { + JButton okButton = new JButton("Save"); + okButton.setActionCommand("OK"); + buttonPane.add(okButton, BorderLayout.EAST); + getRootPane().setDefaultButton(okButton); + okButton.addActionListener((evt) -> { + // Hier später die Daten abspeichern, wenn Datenmodell implementiert ist + dispose(); + }); + } + { + JButton cancelButton = new JButton("Cancel"); + cancelButton.setActionCommand("Cancel"); + buttonPane.add(cancelButton, BorderLayout.WEST); + + cancelButton.addActionListener((evt) -> { dispose(); }); + } + } + } + + /** + * Builds the Settings screen.
+ * Use preferreably since everyone is already initialised.
+ * It personalises the screen more. + * + * @param Username The name of the User + * @param Email The Email that is associated with that Account + * @since Envoy v0.1-alpha + */ + public SettingsScreen(String Username) {// , String Email, String hashedPwd) {AUSKLAMMERN, WENN ANMELDUNG PER EMAIL + // IMPLEMENTIERT IST! + setBackground(Color.BLACK); + setBounds(100, 100, 450, 300); + getContentPane().setLayout(new BorderLayout()); + contentPanel.setBackground(Color.BLACK); + contentPanel.setLayout(new FlowLayout()); + contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); + getContentPane().add(contentPanel, BorderLayout.CENTER); + { + JPanel buttonPane = new JPanel(); + buttonPane.setBackground(Color.BLACK); + getContentPane().add(buttonPane, BorderLayout.SOUTH); + buttonPane.setLayout(new BorderLayout(0, 0)); + { + JButton okButton = new JButton("Save"); + okButton.setActionCommand("OK"); + buttonPane.add(okButton, BorderLayout.EAST); + getRootPane().setDefaultButton(okButton); + okButton.addActionListener((evt) -> { + // Hier später die Daten abspeichern, wenn Datenmodell implementiert ist + dispose(); + }); + } + { + JButton cancelButton = new JButton("Cancel"); + cancelButton.setActionCommand("Cancel"); + buttonPane.add(cancelButton, BorderLayout.WEST); + + cancelButton.addActionListener((evt) -> { dispose(); }); + } + } + } + + /** + * @return true if Enter should be used to send a message instead of ctrl+enter + * @since Envoy v0.1-alpha + */ + public static boolean isEnterToSend() { return enterToSend; } + + /** + * @param enterToSend
+ * toggles whether a message should be sent via + *
+ * buttonpress "enter" or "ctrl"+"enter" + * @since Envoy v0.1-alpha + */ + public static void setEnterToSend(boolean enterForSend) { enterToSend = enterForSend; } + // TODO: Should be changed to private, but later to avoid warnings +} diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java new file mode 100644 index 0000000..c3a3baa --- /dev/null +++ b/src/main/java/envoy/client/ui/Startup.java @@ -0,0 +1,73 @@ +package envoy.client.ui; + +import java.awt.EventQueue; +import java.io.IOException; +import java.util.Properties; + +import javax.swing.JOptionPane; + +import envoy.client.Client; +import envoy.client.Config; +import envoy.client.LocalDB; +import envoy.exception.EnvoyException; + +/** + * Starts the Envoy client and prompts the user to enter their name. + * + * Project: envoy-client
+ * File: Startup.java
+ * Created: 12 Oct 2019
+ * + * @author Leon Hofmeister + * @author Maximilian Käfer + * @since Envoy v0.1-alpha + */ +public class Startup { + + public static void main(String[] args) { + Config config = Config.getInstance(); + if (args.length > 0) { + config.load(args); + } else { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + try { + Properties configProperties = new Properties(); + configProperties.load(loader.getResourceAsStream("client.properties")); + config.load(configProperties); + } catch (IOException e) { + e.printStackTrace(); + } + } + + if (!config.isInitialized()) { + System.err.println("Server or port are not defined. Exiting..."); + System.exit(1); + } + + String userName = JOptionPane.showInputDialog("Please enter your username"); + if (userName == null || userName.isEmpty()) { + System.err.println("User name is not set or empty. Exiting..."); + System.exit(1); + } + Client client = new Client(config, userName); + LocalDB localDB = new LocalDB(client.getSender()); + try { + localDB.initializeDBFile(config.getLocalDB()); + } catch (EnvoyException e) { + e.printStackTrace(); + JOptionPane.showMessageDialog(null, + "Error while loading local database: " + e.toString() + "\nChats will not be stored locally.", + "Local DB error", + JOptionPane.WARNING_MESSAGE); + } + + EventQueue.invokeLater(() -> { + try { + ChatWindow frame = new ChatWindow(client, localDB); + frame.setVisible(true); + } catch (Exception e) { + e.printStackTrace(); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/envoy/client/ui/UserListRenderer.java b/src/main/java/envoy/client/ui/UserListRenderer.java new file mode 100644 index 0000000..40bb2ad --- /dev/null +++ b/src/main/java/envoy/client/ui/UserListRenderer.java @@ -0,0 +1,65 @@ +package envoy.client.ui; + +import java.awt.Component; + +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.ListCellRenderer; + +import envoy.schema.User; +import envoy.schema.User.UserStatus; + +/** + * Defines how the {@code UserList} is displayed. + * + * Project: envoy-client
+ * File: UserListRenderer.java
+ * Created: 12 Oct 2019
+ * + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @since Envoy v0.1-alpha + */ +public class UserListRenderer extends JLabel implements ListCellRenderer { + + private static final long serialVersionUID = 5164417379767181198L; + + @Override + public Component getListCellRendererComponent(JList list, User value, int index, boolean isSelected, + boolean cellHasFocus) { + if (isSelected) { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } else { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + + // Enable background rendering + setOpaque(true); + + + final String name = value.getName(); + final UserStatus status = value.getStatus(); + + switch (status) { + case ONLINE: + setText(String.format( + "

%s

%s", + status, + name)); + break; + + case OFFLINE: + setText(String.format( + "

%s

%s", + status, + name)); + break; + } + + + + return this; + } +} \ No newline at end of file diff --git a/src/main/resources/client.properties b/src/main/resources/client.properties new file mode 100644 index 0000000..2641927 --- /dev/null +++ b/src/main/resources/client.properties @@ -0,0 +1,3 @@ +server=http://kske.feste-ip.net +port=43315 +localDB=.\\localDB