diff --git a/src/main/java/envoy/client/Client.java b/src/main/java/envoy/client/Client.java index 8f96739..b7cc83c 100644 --- a/src/main/java/envoy/client/Client.java +++ b/src/main/java/envoy/client/Client.java @@ -1,6 +1,7 @@ package envoy.client; -import java.util.logging.Logger; +import java.util.HashMap; +import java.util.Map; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; @@ -10,6 +11,7 @@ import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; +import envoy.exception.EnvoyException; import envoy.schema.ObjectFactory; import envoy.schema.Sync; import envoy.schema.User; @@ -29,14 +31,28 @@ public class Client { private ObjectFactory objectFactory = new ObjectFactory(); private Config config; private User sender, recipient; + private boolean online = false; - private static final Logger logger = Logger.getLogger(Client.class.getSimpleName()); + /** + * Initializes the client. At this state, the client user has yet to be + * initialized, which can be done by calling {@link Client#onlineInit(String). + * + * @param config The {@link Config} instance to use in this client + * @since Envoy v0.2-alpha + */ + public Client(Config config) { this.config = config; } - public Client(Config config, String username) { - this.config = config; - sender = getUser(username); - - logger.info("ID: " + sender.getID()); + /** + * Enters the online mode by acquiring a user ID from the server. + * + * @param userName the name of the client user + * @throws EnvoyException if the online mode could not be entered or the request + * failed for some other reason + * @since Envoy v0.2-alpha + */ + public void onlineInit(String userName) throws EnvoyException { + sender = getUser(userName); + online = true; } private R post(String uri, T body, Class responseBodyClass) { @@ -48,28 +64,26 @@ public class Client { 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 + * @return a {@code Map} of all users on the server with their + * user names as keys + * @since Envoy v0.2-alpha */ - public Sync getUsersListXml() { + public Map getUsers() { 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), + Sync returnSync = post(String.format("%s:%d/envoy-server/rest/sync/syncData?userId=%d", config.getServer(), config.getPort(), 0), sendSync, Sync.class); - return returnSendSync; + Map users = new HashMap<>(); + returnSync.getUsers().forEach(u -> users.put(u.getName(), u)); + return users; } /** @@ -77,29 +91,29 @@ public class Client { * * @param name - the name of the {@link User} * @return a {@link User} with the specified name + * @throws EnvoyException if the server does not return the requested ID * @since Envoy v0.1-alpha */ - private User getUser(String name) { + private User getUser(String name) throws EnvoyException { + // Create a sync with only a user with the requested 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); + try { + Sync sync = 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 { - logger.warning("ERROR exiting..."); + // Expecting a single user with an ID + if (sync.getUsers().size() == 1) { + online = true; + return sync.getUsers().get(0); + } else throw new EnvoyException("Unexpected response from Envoy Server"); + } catch (Exception e) { + throw new EnvoyException("Could not connect to server", e); } - - return returnSender; } /** @@ -134,11 +148,15 @@ public class Client { * their updated UserStatus to the client.)
* * @param userId the id of the {@link Client} who sends the {@link Sync} - * @param sync the sync object (yet to be converted from java class to sync.xml) + * @param sync the sync object (yet to be converted from java class to + * sync.xml) * @return a returnSync.xml file + * @throws EnvoyException if the client is not in online mode * @since Envoy v0.1-alpha */ - public Sync sendSync(long userId, Sync sync) { + public Sync sendSync(long userId, Sync sync) throws EnvoyException { + if(!isOnline()) + throw new EnvoyException("Client is not in online mode"); // Print sync XML to console JAXBContext jc; try { @@ -151,10 +169,7 @@ public class Client { } // Send sync - return post(String - .format("%s:%d/envoy-server/rest/sync/syncData?userId=%d", config.getServer(), config.getPort(), userId), - sync, - Sync.class); + return post(String.format("%s:%d/envoy-server/rest/sync/syncData?userId=%d", config.getServer(), config.getPort(), userId), sync, Sync.class); } /** @@ -163,6 +178,14 @@ public class Client { */ public User getSender() { return sender; } + /** + * Sets the client user which is used to send messages. + * + * @param sender the client user to set + * @since Envoy v0.2-alpha + */ + public void setSender(User sender) { this.sender = sender; } + /** * @return the current recipient of the current chat. * @since Envoy v0.1-alpha @@ -172,7 +195,7 @@ public class Client { /** * Sets the recipient. * - * @param recipient - the recipient to set + * @param recipient the recipient to set * @since Envoy v0.1-alpha */ public void setRecipient(User recipient) { this.recipient = recipient; } @@ -182,4 +205,10 @@ public class Client { * @since Envoy v0.1-alpha */ public boolean hasRecipient() { return recipient != null; } + + /** + * @return {@code true} if a connection to the server could be established + * @since Envoy v0.2-alpha + */ + public boolean isOnline() { return online; } } diff --git a/src/main/java/envoy/client/Config.java b/src/main/java/envoy/client/Config.java index bbd3b72..0c3a000 100644 --- a/src/main/java/envoy/client/Config.java +++ b/src/main/java/envoy/client/Config.java @@ -3,6 +3,8 @@ package envoy.client; import java.io.File; import java.util.Properties; +import envoy.exception.EnvoyException; + /** * Project: envoy-client
* File: Config.java
@@ -29,17 +31,26 @@ public class Config { /** * Defaults to the {@code client.properties} file for information. + * This file contains information about + * the server and port, as well as the path to the local + * database and the synchronization timeout * - * @param properties a {@link Properties} object containing information about - * the server and port, as well as the path to the local - * database and the synchronization timeout + * @throws EnvoyException if the {@code client.properties} file could not be + * loaded * @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")); + public void load() throws EnvoyException { + ClassLoader loader = getClass().getClassLoader(); + try { + Properties properties = new Properties(); + properties.load(loader.getResourceAsStream("client.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")); + } catch (Exception e) { + throw new EnvoyException("Failed to load client.properties", e); + } } /** @@ -47,9 +58,10 @@ public class Config { * -s, --port / -p and --localDB / -db. * * @param args the command line arguments to parse + * @throws EnvoyException if the command line arguments contain an unknown token * @since Envoy v0.1-alpha */ - public void load(String[] args) { + public void load(String[] args) throws EnvoyException { for (int i = 0; i < args.length; i++) switch (args[i]) { case "--server": @@ -64,6 +76,8 @@ public class Config { case "-db": localDB = new File(args[++i]); break; + default: + throw new EnvoyException("Unknown token " + args[i] + " found"); } } @@ -71,9 +85,7 @@ public class Config { * @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; - } + public boolean isInitialized() { return server != null && !server.isEmpty() && port > 0 && port < 65566 && localDB != null; } /** * @return the host name of the Envoy server @@ -114,7 +126,7 @@ public class Config { /** * Changes the default local database. * Exclusively intended for development purposes. - * + * * @param localDB the file containing the local database * @since Envoy v0.1-alpha **/ diff --git a/src/main/java/envoy/client/LocalDB.java b/src/main/java/envoy/client/LocalDB.java index f061b28..60de119 100644 --- a/src/main/java/envoy/client/LocalDB.java +++ b/src/main/java/envoy/client/LocalDB.java @@ -8,7 +8,9 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.time.Instant; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Logger; import javax.xml.datatype.DatatypeConfigurationException; @@ -34,9 +36,11 @@ import envoy.schema.User; */ public class LocalDB { - private File localDB; - private User sender; - private List chats = new ArrayList<>(); + private File localDBDir, localDBFile, usersFile; + private User user; + private Map users = new HashMap<>(); + private List chats = new ArrayList<>(); + private ObjectFactory objectFactory = new ObjectFactory(); private DatatypeFactory datatypeFactory; @@ -44,67 +48,94 @@ public class LocalDB { private Sync sync = objectFactory.createSync(); private Sync readMessages = objectFactory.createSync(); - private static final Logger logger = Logger.getLogger(LocalDB.class.getSimpleName()); + private static final Logger logger = Logger.getLogger(LocalDB.class.getSimpleName()); /** - * Constructs an empty local database. + * Constructs an empty local database. To serialize any chats to the file + * system, call {@link LocalDB#initializeDBFile(File)}. * - * @param sender the user that is logged in with this client + * @param localDBDir the directory in which to store users and chats * @since Envoy v0.1-alpha */ - public LocalDB(User sender) { - this.sender = sender; + public LocalDB(File localDBDir) throws IOException { + this.localDBDir = localDBDir; + try { datatypeFactory = DatatypeFactory.newInstance(); } catch (DatatypeConfigurationException e) { e.printStackTrace(); } + + // 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"); } /** - * Initializes the local database and fills it with values - * if the user has already sent or received messages. + * Creates a database file for a user-specific list of chats. * - * @param localDBDir the directory where we wish to save/load the database from. - * @throws EnvoyException if the directory selected is not an actual directory. + * @throws NullPointerException if the client user is not yet specified * @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(); + public void initializeDBFile() { + if (user == null) throw new NullPointerException("Client user is null"); + localDBFile = new File(localDBDir, user.getID() + ".db"); } /** - * Saves the database into a file for future use. + * Stores all users to the local database. If the client user is specified, the + * chats related to this user are stored as well. * * @throws IOException if something went wrong during saving * @since Envoy v0.1-alpha */ - public void saveToLocalDB() throws IOException { - localDB.getParentFile().mkdirs(); - localDB.createNewFile(); - try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) { - out.writeObject(chats); - } catch (IOException ex) { - throw ex; - } + public void save() throws IOException { + // Save users + write(usersFile, users); + + // Save chats + write(localDBFile, chats); } + /** + * Loads all users that are stored in the local database. + * + * @throws EnvoyException if the loading process failed + * @since Envoy v0.2-alpha + */ + @SuppressWarnings("unchecked") + public void loadUsers() throws EnvoyException { users = read(usersFile, HashMap.class); } + /** * Loads all chats saved by Envoy for the client user. * - * @throws EnvoyException if something fails while loading. + * @throws EnvoyException if the loading process failed * @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; + public void loadChats() throws EnvoyException { chats = read(localDBFile, ArrayList.class); } + + private T read(File file, Class serializedClass) throws EnvoyException { + if (file == null) throw new NullPointerException("File is null"); + try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) { + return serializedClass.cast(in.readObject()); } catch (ClassNotFoundException | IOException e) { - throw new EnvoyException(e); + throw new EnvoyException("Could not load serialized object", e); + } + } + + private void write(File file, T obj) throws IOException { + if (file == null) throw new NullPointerException("File is null"); + if (obj == null) throw new NullPointerException("Object to serialize is null"); + if (!file.exists()) { + file.getParentFile().mkdirs(); + file.createNewFile(); + } + try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) { + out.writeObject(obj); + } catch (IOException e) { + throw e; } } @@ -118,7 +149,7 @@ public class LocalDB { */ public Message createMessage(String textContent, long recipientID) { Message.Metadata metaData = objectFactory.createMessageMetadata(); - metaData.setSender(sender.getID()); + metaData.setSender(user.getID()); metaData.setRecipient(recipientID); metaData.setState(MessageState.WAITING); metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString())); @@ -134,13 +165,13 @@ public class LocalDB { return message; } - /** + /** * Creates a {@link Sync} object filled with the changes that occurred to the * local database since the last synchronization. * * @param userId the ID of the user that is synchronized by this client * @return {@link Sync} object filled with the current changes - * @since Envoy v0.1-alpha + * @since Envoy v0.1-alpha */ public Sync fillSync(long userId) { addWaitingMessagesToSync(); @@ -156,7 +187,7 @@ public class LocalDB { * Applies the changes carried by a {@link Sync} object to the local database * * @param returnSync the {@link Sync} object to apply - * @since Envoy v0.1-alpha + * @since Envoy v0.1-alpha */ public void applySync(Sync returnSync) { for (int i = 0; i < returnSync.getMessages().size(); i++) { @@ -280,6 +311,17 @@ public class LocalDB { */ public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); } + /** + * @return a {@code Map} of all users stored locally with their user names as keys + * @since Envoy v0.2-alpha + */ + public Map getUsers() { return users; } + + /** + * @param users the users to set + */ + public void setUsers(Map users) { this.users = users; } + /** * @return all saved {@link Chat} objects that list the client user as the * sender @@ -288,8 +330,19 @@ public class LocalDB { public List getChats() { return chats; } /** - * @return the {@link User} who initialized the local database - * @since Envoy v0.1-alpha + * @param chats the chats to set */ - public User getUser() { return sender; } + public void setChats(List chats) { this.chats = chats; } + + /** + * @return the {@link User} who initialized the local database + * @since Envoy v0.2-alpha + */ + public User getUser() { return user; } + + /** + * @param user the user to set + * @since Envoy v0.2-alpha + */ + public void setUser(User user) { this.user = user; } } diff --git a/src/main/java/envoy/client/Settings.java b/src/main/java/envoy/client/Settings.java index 3bd54f1..6ac095e 100644 --- a/src/main/java/envoy/client/Settings.java +++ b/src/main/java/envoy/client/Settings.java @@ -26,8 +26,6 @@ import envoy.client.ui.Theme; public class Settings { // Actual settings accessible by the rest of the application - private String username; - private String email; private boolean enterToSend = true; private Map themes; private String currentTheme; @@ -65,17 +63,15 @@ public class Settings { @SuppressWarnings("unchecked") private void load() { - setUsername(prefs.get("username", "")); - setEmail(prefs.get("email", "")); setEnterToSend(prefs.getBoolean("enterToSend", true)); setCurrentTheme(prefs.get("theme", "dark")); // Load themes from theme file try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(themeFile))) { Object obj = in.readObject(); - if(obj instanceof HashMap) themes = (Map) obj; + if (obj instanceof HashMap) themes = (Map) obj; } catch (IOException | ClassNotFoundException e) { - themes = new HashMap<>(); + themes = new HashMap<>(); currentTheme = "dark"; e.printStackTrace(); } @@ -90,20 +86,18 @@ public class Settings { } /** - * updates prefs when save button is clicked + * Updates the preferences when the save button is clicked. * * @throws IOException * @since Envoy v0.2-alpha */ - public void save() throws IOException{ - prefs.put("username", getUsername()); - prefs.put("email", getEmail()); + public void save() throws IOException { prefs.put("theme", currentTheme); prefs.putBoolean("enterToSend", isEnterToSend()); - + // Save themes to theme file themeFile.createNewFile(); - try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(themeFile))) { + try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(themeFile))) { out.writeObject(themes); } } @@ -133,30 +127,6 @@ public class Settings { */ public void setCurrentTheme(String themeName) { currentTheme = themeName; } - /** - * @return the user name - * @since Envoy v0.2-alpha - */ - public String getUsername() { return username; } - - /** - * @param username the user name to set - * @since Envoy v0.2-alpha - */ - public void setUsername(String username) { this.username = username; } - - /** - * @return the email associated with that user. - * @since Envoy v0.2-alpha - */ - public String getEmail() { return email; } - - /** - * @param email the email to set - * @since Envoy v0.2-alpha - */ - public void setEmail(String email) { this.email = email; } - /** * @return true, if "enter" suffices to send a message, else it has to be "ctrl" * + "enter" diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index 14065b0..1c562b6 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -32,7 +32,6 @@ import envoy.client.Config; import envoy.client.LocalDB; import envoy.client.Settings; import envoy.schema.Message; -import envoy.schema.Sync; import envoy.schema.User; /** @@ -54,15 +53,15 @@ public class ChatWindow extends JFrame { private LocalDB localDB; // GUI components private JPanel contentPane = new JPanel(); - private PrimaryTextArea messageEnterTextArea; + private PrimaryTextArea messageEnterTextArea = new PrimaryTextArea(space); private JList userList = new JList<>(); private Chat currentChat; private JList messageList = new JList<>(); private JScrollPane scrollPane = new JScrollPane(); private JTextPane textPane = new JTextPane(); // private JCheckBox jCbChangeMode; - private PrimaryButton postButton; - private PrimaryButton settingsButton; + private PrimaryButton postButton = new PrimaryButton("Post"); + private PrimaryButton settingsButton = new PrimaryButton("Settings"); private static int space = 4; @@ -84,7 +83,7 @@ public class ChatWindow extends JFrame { @Override public void windowClosing(WindowEvent evt) { try { - localDB.saveToLocalDB(); + localDB.save(); Settings.getInstance().save(); } catch (IOException e1) { e1.printStackTrace(); @@ -124,23 +123,18 @@ public class ChatWindow extends JFrame { gbc_scrollPane.insets = new Insets(space, space, space, space); contentPane.add(scrollPane, gbc_scrollPane); - // Checks for changed Message - messageEnterTextArea = new PrimaryTextArea(space); - // Message enter field messageEnterTextArea.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER - && ((Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0) - || (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) { + && ((Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0) || (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) { postMessage(messageList); } } }); - GridBagConstraints gbc_messageEnterTextfield = new GridBagConstraints(); gbc_messageEnterTextfield.fill = GridBagConstraints.BOTH; gbc_messageEnterTextfield.gridx = 1; @@ -151,7 +145,6 @@ public class ChatWindow extends JFrame { contentPane.add(messageEnterTextArea, gbc_messageEnterTextfield); // Post Button - postButton = new PrimaryButton("Post"); GridBagConstraints gbc_moveSelectionPostButton = new GridBagConstraints(); gbc_moveSelectionPostButton.fill = GridBagConstraints.BOTH; @@ -164,8 +157,6 @@ public class ChatWindow extends JFrame { contentPane.add(postButton, gbc_moveSelectionPostButton); // Settings Button - settingsButton = new PrimaryButton("Settings"); - GridBagConstraints gbc_moveSelectionSettingsButton = new GridBagConstraints(); gbc_moveSelectionSettingsButton.fill = GridBagConstraints.BOTH; @@ -177,8 +168,8 @@ public class ChatWindow extends JFrame { settingsButton.addActionListener((evt) -> { try { SettingsScreen.open(); - changeChatWindowColors(Settings.getInstance().getCurrentTheme()); - } catch (Exception e) { + changeChatWindowColors(Settings.getInstance().getCurrentTheme()); + } catch (Exception e) { SettingsScreen.open(); logger.log(Level.WARNING, "An error occured while opening the settings screen", e); e.printStackTrace(); @@ -231,16 +222,16 @@ public class ChatWindow extends JFrame { gbc_userList.insets = new Insets(space, space, space, space); changeChatWindowColors(Settings.getInstance().getCurrentTheme()); - + contentPane.add(userList, gbc_userList); contentPane.revalidate(); loadUsersAndChats(); - startSyncThread(Config.getInstance().getSyncTimeout()); + + if (client.isOnline()) startSyncThread(Config.getInstance().getSyncTimeout()); contentPane.revalidate(); } - /** * Used to immediately reload the ChatWindow when settings were changed. @@ -283,18 +274,14 @@ public class ChatWindow extends JFrame { private void postMessage(JList messageList) { if (!client.hasRecipient()) { - JOptionPane.showMessageDialog(this, - "Please select a recipient!", - "Cannot send message", - JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE); return; } if (!messageEnterTextArea.getText().isEmpty()) try { // Create and send message object - final Message message = localDB.createMessage(messageEnterTextArea.getText(), - currentChat.getRecipient().getID()); + final Message message = localDB.createMessage(messageEnterTextArea.getText(), currentChat.getRecipient().getID()); currentChat.appendMessage(message); messageList.setModel(currentChat.getModel()); @@ -318,9 +305,8 @@ public class ChatWindow extends JFrame { */ private void loadUsersAndChats() { new Thread(() -> { - Sync users = client.getUsersListXml(); - DefaultListModel userListModel = new DefaultListModel<>(); - users.getUsers().forEach(user -> { + DefaultListModel userListModel = new DefaultListModel<>(); + localDB.getUsers().values().forEach(user -> { userListModel.addElement(user); // Check if user exists in local DB @@ -344,8 +330,11 @@ public class ChatWindow extends JFrame { new Thread(() -> { // Synchronize - localDB.applySync( - client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID()))); + try { + localDB.applySync(client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID()))); + } catch (Exception e) { + logger.log(Level.SEVERE, "Could not perform sync", e); + } // Process unread messages localDB.addUnreadMessagesToLocalDB(); @@ -355,8 +344,7 @@ public class ChatWindow extends JFrame { readCurrentChat(); // Update UI - SwingUtilities - .invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); }); + SwingUtilities.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); }); }).start(); }).start(); } @@ -373,4 +361,3 @@ public class ChatWindow extends JFrame { */ 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 index 2e6b65f..9be79c5 100644 --- a/src/main/java/envoy/client/ui/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/MessageListRenderer.java @@ -42,22 +42,18 @@ public class MessageListRenderer extends JLabel implements ListCellRenderer

%s

%s :%s", + setText(String.format("

%s

%s :%s", dateColor, date, textColor, diff --git a/src/main/java/envoy/client/ui/PrimaryButton.java b/src/main/java/envoy/client/ui/PrimaryButton.java index 2e528db..fe93571 100644 --- a/src/main/java/envoy/client/ui/PrimaryButton.java +++ b/src/main/java/envoy/client/ui/PrimaryButton.java @@ -11,6 +11,7 @@ import javax.swing.JButton; * * @author Kai S. K. Engelbart * @author Maximilian Käfer + * @since Envoy v0.2-alpha */ public class PrimaryButton extends JButton { @@ -35,8 +36,6 @@ public class PrimaryButton extends JButton { */ public PrimaryButton(String title, int arcSize) { super(title); - // setForeground(new Color(255, 255, 255)); - // setBackground(new Color(102, 51, 153)); setBorderPainted(false); setFocusPainted(false); setContentAreaFilled(false); diff --git a/src/main/java/envoy/client/ui/PrimaryTextArea.java b/src/main/java/envoy/client/ui/PrimaryTextArea.java index 3f350ed..1fd5584 100644 --- a/src/main/java/envoy/client/ui/PrimaryTextArea.java +++ b/src/main/java/envoy/client/ui/PrimaryTextArea.java @@ -12,15 +12,15 @@ import javax.swing.border.EmptyBorder; * Created: 07.12.2019
* * @author Maximilian Käfer + * @since Envoy v0.2-alpha */ public class PrimaryTextArea extends JTextArea { - private static final long serialVersionUID = 1L; - - private int arcSize; + private static final long serialVersionUID = -5829028696155434913L; + private int arcSize; /** - * Creates TextArea + * Creates the text area * * @param borderSpace * @since Envoy 0.2-alpha @@ -28,10 +28,10 @@ public class PrimaryTextArea extends JTextArea { public PrimaryTextArea(int borderSpace) { this(6, borderSpace); } /** - * Creates TextArea + * Creates the text area * - * @param arcSize - * @param borderSpace + * @param arcSize is the diameter of the arc at the four corners. + * @param borderSpace is the insets of the border on all four sides. * @since Envoy 0.2-alpha */ public PrimaryTextArea(int arcSize, int borderSpace) { @@ -54,7 +54,7 @@ public class PrimaryTextArea extends JTextArea { } /** - * @return the arcSize + * @return the arcSize - the diameter of the arc at the four corners. * @since Envoy 0.2-alpha */ public int getArcSize() { return arcSize; } diff --git a/src/main/java/envoy/client/ui/SettingsScreen.java b/src/main/java/envoy/client/ui/SettingsScreen.java index ef1dadc..161365d 100644 --- a/src/main/java/envoy/client/ui/SettingsScreen.java +++ b/src/main/java/envoy/client/ui/SettingsScreen.java @@ -101,8 +101,7 @@ public class SettingsScreen extends JDialog { createNewThemeButton.setEnabled(false); - temporaryTheme = new Theme("temporaryTheme", - Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); + temporaryTheme = new Theme("temporaryTheme", Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); // Content pane GridBagLayout gbl_contentPanel = new GridBagLayout(); @@ -190,20 +189,8 @@ public class SettingsScreen extends JDialog { colorsPanel.setLayout(new BoxLayout(colorsPanel, BoxLayout.Y_AXIS)); colorsPanel.setAlignmentX(Component.LEFT_ALIGNMENT); - buildCustomizeElement(new JPanel(), - new JButton(), - new JTextPane(), - theme, - theme.getBackgroundColor(), - "Background", - 0); - buildCustomizeElement(new JPanel(), - new JButton(), - new JTextPane(), - theme, - theme.getCellColor(), - "Cells", - 1); + buildCustomizeElement(new JPanel(), new JButton(), new JTextPane(), theme, theme.getBackgroundColor(), "Background", 0); + buildCustomizeElement(new JPanel(), new JButton(), new JTextPane(), theme, theme.getCellColor(), "Cells", 1); buildCustomizeElement(new JPanel(), new JButton(), new JTextPane(), @@ -218,41 +205,11 @@ public class SettingsScreen extends JDialog { theme.getInteractableBackgroundColor(), "Interactable Background", 3); - buildCustomizeElement(new JPanel(), - new JButton(), - new JTextPane(), - theme, - theme.getMessageColorChat(), - "Messages Chat", - 4); - buildCustomizeElement(new JPanel(), - new JButton(), - new JTextPane(), - theme, - theme.getDateColorChat(), - "Date Chat", - 5); - buildCustomizeElement(new JPanel(), - new JButton(), - new JTextPane(), - theme, - theme.getSelectionColor(), - "Selection", - 6); - buildCustomizeElement(new JPanel(), - new JButton(), - new JTextPane(), - theme, - theme.getTypingMessageColor(), - "Typing Message", - 7); - buildCustomizeElement(new JPanel(), - new JButton(), - new JTextPane(), - theme, - theme.getUserNameColor(), - "User Names", - 8); + buildCustomizeElement(new JPanel(), new JButton(), new JTextPane(), theme, theme.getMessageColorChat(), "Messages Chat", 4); + buildCustomizeElement(new JPanel(), new JButton(), new JTextPane(), theme, theme.getDateColorChat(), "Date Chat", 5); + buildCustomizeElement(new JPanel(), new JButton(), new JTextPane(), theme, theme.getSelectionColor(), "Selection", 6); + buildCustomizeElement(new JPanel(), new JButton(), new JTextPane(), theme, theme.getTypingMessageColor(), "Typing Message", 7); + buildCustomizeElement(new JPanel(), new JButton(), new JTextPane(), theme, theme.getUserNameColor(), "User Names", 8); GridBagConstraints gbc_colorsPanel = new GridBagConstraints(); gbc_colorsPanel.fill = GridBagConstraints.HORIZONTAL; @@ -273,16 +230,14 @@ public class SettingsScreen extends JDialog { String s = JOptionPane.showInputDialog("Enter a name for the new theme"); System.out.println(s); Settings.getInstance() - .addNewThemeToMap(new Theme(s, temporaryTheme.getBackgroundColor(), - temporaryTheme.getCellColor(), temporaryTheme.getInteractableForegroundColor(), - temporaryTheme.getInteractableBackgroundColor(), temporaryTheme.getMessageColorChat(), - temporaryTheme.getDateColorChat(), temporaryTheme.getSelectionColor(), + .addNewThemeToMap(new Theme(s, temporaryTheme.getBackgroundColor(), temporaryTheme.getCellColor(), + temporaryTheme.getInteractableForegroundColor(), temporaryTheme.getInteractableBackgroundColor(), + temporaryTheme.getMessageColorChat(), temporaryTheme.getDateColorChat(), temporaryTheme.getSelectionColor(), temporaryTheme.getTypingMessageColor(), temporaryTheme.getUserNameColor())); themeArray = Arrays.copyOf(themeArray, themeArray.length + 1); themeArray[themeArray.length - 1] = Settings.getInstance().getThemes().get(s).getThemeName(); - temporaryTheme = new Theme("temporaryTheme", - Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); + temporaryTheme = new Theme("temporaryTheme", Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme())); createNewThemeButton.setEnabled(false); themes.addItem(themeArray[themeArray.length - 1]); @@ -336,10 +291,6 @@ public class SettingsScreen extends JDialog { getRootPane().setDefaultButton(okButton); okButton.addActionListener((evt) -> { try { - Settings.getInstance().setUsername(Settings.getInstance().getUsername());// still temporary - - Settings.getInstance().setEmail(Settings.getInstance().getEmail());// still temporary value - Settings.getInstance().setEnterToSend(Settings.getInstance().isEnterToSend());// still temporary Settings.getInstance().setCurrentTheme(selectedTheme.getThemeName()); @@ -400,21 +351,12 @@ public class SettingsScreen extends JDialog { temporaryTheme = new Theme("temporaryTheme", Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getBackgroundColor(), Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getCellColor(), - Settings.getInstance() - .getThemes() - .get(Settings.getInstance().getCurrentTheme()) - .getInteractableForegroundColor(), - Settings.getInstance() - .getThemes() - .get(Settings.getInstance().getCurrentTheme()) - .getInteractableBackgroundColor(), + Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getInteractableForegroundColor(), + Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getInteractableBackgroundColor(), Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getMessageColorChat(), Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getDateColorChat(), Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getSelectionColor(), - Settings.getInstance() - .getThemes() - .get(Settings.getInstance().getCurrentTheme()) - .getTypingMessageColor(), + Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getTypingMessageColor(), Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getUserNameColor()); colorsPanel.removeAll(); @@ -501,8 +443,7 @@ public class SettingsScreen extends JDialog { private void setContent(JPanel content, GridBagConstraints layout) { contentPanel.add(content, layout); } - private void buildCustomizeElement(JPanel panel, JButton button, JTextPane textPane, Theme theme, Color color, - String name, int yIndex) { + private void buildCustomizeElement(JPanel panel, JButton button, JTextPane textPane, Theme theme, Color color, String name, int yIndex) { textPane.setFont(new Font("Arial", Font.PLAIN, 14)); textPane.setBackground(theme.getBackgroundColor()); textPane.setForeground(getInvertedColor(theme.getBackgroundColor())); diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 25af819..933890d 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -2,7 +2,6 @@ package envoy.client.ui; import java.awt.EventQueue; import java.io.IOException; -import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -11,12 +10,12 @@ import javax.swing.JOptionPane; import envoy.client.Client; import envoy.client.Config; import envoy.client.LocalDB; -import envoy.client.Settings; import envoy.exception.EnvoyException; +import envoy.schema.User; /** * Starts the Envoy client and prompts the user to enter their name. - * + *
* Project: envoy-client
* File: Startup.java
* Created: 12 Oct 2019
@@ -35,34 +34,69 @@ public class Startup { Config config = Config.getInstance(); - // Load the configuration from client.properties first - ClassLoader loader = Thread.currentThread().getContextClassLoader(); try { - Properties configProperties = new Properties(); - configProperties.load(loader.getResourceAsStream("client.properties")); - config.load(configProperties); - } catch (IOException e) { + // Load the configuration from client.properties first + config.load(); + + // Override configuration values with command line arguments + if (args.length > 0) config.load(args); + + // Check if all configuration values have been initialized + if (!config.isInitialized()) throw new EnvoyException("Server or port are not defined"); + } catch (Exception e) { + JOptionPane + .showMessageDialog(null, "Error loading configuration values: \n" + e.toString(), "Configuration error", JOptionPane.ERROR_MESSAGE); + System.exit(1); e.printStackTrace(); } - // Override configuration values with command line arguments - if (args.length > 0) config.load(args); - - if (!config.isInitialized()) { - logger.severe("Server or port are not defined. Exiting..."); - JOptionPane.showMessageDialog(null, "Error loading configuration values.", "Configuration error", JOptionPane.ERROR_MESSAGE); - System.exit(1); - } - + // Ask the user for their user name String userName = JOptionPane.showInputDialog("Please enter your username"); if (userName == null || userName.isEmpty()) { logger.severe("User name is not set or empty. Exiting..."); System.exit(1); } - Client client = new Client(config, userName); - LocalDB localDB = new LocalDB(client.getSender()); + + // Initialize the local database + LocalDB localDB; try { - localDB.initializeDBFile(config.getLocalDB()); + localDB = new LocalDB(config.getLocalDB()); + } catch (IOException e3) { + logger.log(Level.SEVERE, "Could not initialize local database", e3); + JOptionPane.showMessageDialog(null, "Could not initialize local database!\n" + e3.toString()); + System.exit(1); + return; + } + + // Acquire the client user (with ID) either from the server or from the local + // database, which triggers offline mode + Client client = new Client(config); + try { + // Try entering online mode first + client.onlineInit(userName); + } catch (Exception e1) { + logger.warning("Could not connect to server. Trying offline mode..."); + try { + // Try entering offline mode + localDB.loadUsers(); + User clientUser = localDB.getUsers().get(userName); + if(clientUser == null) + throw new EnvoyException("Could not enter offline mode: user name unknown"); + client.setSender(clientUser); + } catch(Exception e2) { + JOptionPane.showMessageDialog(null, e1.toString(), "Client error", JOptionPane.ERROR_MESSAGE); + System.exit(1); + return; + } + } + + // Set client user in local database + localDB.setUser(client.getSender()); + + // Initialize chats in local database + try { + localDB.initializeDBFile(); + localDB.loadChats(); } catch (EnvoyException e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, @@ -70,13 +104,23 @@ public class Startup { "Local DB error", JOptionPane.WARNING_MESSAGE); } - Settings.getInstance().setUsername(userName); + + logger.info("Client user ID: " + client.getSender().getID()); + + // Save all users to the local database + if(client.isOnline()) + localDB.setUsers(client.getUsers()); EventQueue.invokeLater(() -> { try { ChatWindow chatWindow = new ChatWindow(client, localDB); - new StatusTrayIcon(chatWindow).show(); chatWindow.setVisible(true); + + try { + new StatusTrayIcon(chatWindow).show(); + } catch (EnvoyException e) { + logger.warning("The StatusTrayIcon is not supported on this platform!"); + } } catch (Exception e) { e.printStackTrace(); } diff --git a/src/main/java/envoy/client/ui/Theme.java b/src/main/java/envoy/client/ui/Theme.java index 9dcda1e..976046b 100644 --- a/src/main/java/envoy/client/ui/Theme.java +++ b/src/main/java/envoy/client/ui/Theme.java @@ -26,9 +26,8 @@ public class Theme implements Serializable { private Color selectionColor; private Color typingMessageColor; - public Theme(String themeName, Color backgroundColor, Color cellColor, Color interactableForegroundColor, - Color interactableBackgroundColor, Color messageColorChat, Color dateColorChat, Color selectionColor, - Color typingMessageColor, Color userNameColor) { + public Theme(String themeName, Color backgroundColor, Color cellColor, Color interactableForegroundColor, Color interactableBackgroundColor, + Color messageColorChat, Color dateColorChat, Color selectionColor, Color typingMessageColor, Color userNameColor) { this.themeName = themeName; @@ -42,7 +41,7 @@ public class Theme implements Serializable { this.typingMessageColor = typingMessageColor; this.userNameColor = userNameColor; } - + public Theme(String name, Theme other) { this(name, other.backgroundColor, other.cellColor, other.interactableForegroundColor, other.interactableBackgroundColor, other.messageColorChat, other.dateColorChat, other.selectionColor, diff --git a/src/main/java/envoy/client/ui/UserListRenderer.java b/src/main/java/envoy/client/ui/UserListRenderer.java index c730e74..b145f28 100644 --- a/src/main/java/envoy/client/ui/UserListRenderer.java +++ b/src/main/java/envoy/client/ui/UserListRenderer.java @@ -44,23 +44,16 @@ public class UserListRenderer extends JLabel implements ListCellRenderer { final UserStatus status = value.getStatus(); // Getting the UserNameColor of the current theme - String textColor = null; - textColor = toHex( - Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getUserNameColor()); + String textColor = null; + textColor = toHex(Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getUserNameColor()); switch (status) { case ONLINE: - setText(String.format( - "

%s

%s", - status, - textColor, - name)); + setText(String + .format("

%s

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

%s

%s", - status, - textColor, - name)); + setText(String + .format("

%s

%s", status, textColor, name)); break; } return this;