Implemented offline mode for Client and LocalDB

This commit is contained in:
Kai S. K. Engelbart 2019-12-14 10:53:20 +01:00
parent 5b84578a0a
commit ea3ad85611
4 changed files with 87 additions and 63 deletions

View File

@ -1,6 +1,7 @@
package envoy.client; 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.ClientBuilder;
import javax.ws.rs.client.Entity; import javax.ws.rs.client.Entity;
@ -32,13 +33,11 @@ public class Client {
private User sender, recipient; private User sender, recipient;
private boolean online = false; private boolean online = false;
private static final Logger logger = Logger.getLogger(Client.class.getSimpleName()); public Client(Config config) { this.config = config; }
public Client(Config config, String userName) throws EnvoyException { public void onlineInit(String userName) throws EnvoyException {
this.config = config; sender = getUser(userName);
sender = getUser(userName); online = true;
logger.info("Client user ID: " + sender.getID());
} }
private <T, R> R post(String uri, T body, Class<R> responseBodyClass) { private <T, R> R post(String uri, T body, Class<R> responseBodyClass) {
@ -53,22 +52,25 @@ public class Client {
} }
/** /**
* Returns a {@link Sync} with all users on the server. * Returns a {@code Map<String, User>} of all users on the server with their
* user names as keys.
* *
* @return Sync - List of all users on the server. * @return Sync - List of all users on the server.
* @since Envoy v0.1-alpha * @since Envoy v0.2-alpha
*/ */
public Sync getUsersListXml() { public Map<String, User> getUsers() {
Sync sendSync = objectFactory.createSync(); Sync sendSync = objectFactory.createSync();
User user = objectFactory.createUser(); User user = objectFactory.createUser();
user.setID(-1); user.setID(-1);
sendSync.getUsers().add(user); 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, sendSync,
Sync.class); Sync.class);
return returnSendSync;
Map<String, User> users = new HashMap<>();
returnSync.getUsers().forEach(u -> users.put(u.getName(), u));
return users;
} }
/** /**
@ -160,6 +162,8 @@ public class Client {
*/ */
public User getSender() { return sender; } public User getSender() { return sender; }
public void setSender(User sender) { this.sender = sender; }
/** /**
* @return the current recipient of the current chat. * @return the current recipient of the current chat.
* @since Envoy v0.1-alpha * @since Envoy v0.1-alpha

View File

@ -8,7 +8,9 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeConfigurationException;
@ -34,9 +36,9 @@ import envoy.schema.User;
*/ */
public class LocalDB { public class LocalDB {
private File localDBFile, usersFile; private File localDBDir, localDBFile, usersFile;
private User user; private User user;
private List<User> users = new ArrayList<>(); private Map<String, User> users = new HashMap<>();
private List<Chat> chats = new ArrayList<>(); private List<Chat> chats = new ArrayList<>();
private ObjectFactory objectFactory = new ObjectFactory(); private ObjectFactory objectFactory = new ObjectFactory();
@ -54,12 +56,19 @@ public class LocalDB {
* *
* @since Envoy v0.1-alpha * @since Envoy v0.1-alpha
*/ */
public LocalDB() { public LocalDB(File localDBDir) throws IOException {
this.localDBDir = localDBDir;
try { try {
datatypeFactory = DatatypeFactory.newInstance(); datatypeFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) { } catch (DatatypeConfigurationException e) {
e.printStackTrace(); 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");
} }
/** /**
@ -70,18 +79,9 @@ public class LocalDB {
* @throws EnvoyException if the directory selected is not an actual directory. * @throws EnvoyException if the directory selected is not an actual directory.
* @since Envoy v0.1-alpha * @since Envoy v0.1-alpha
*/ */
public void initializeDBFile(File localDBDir) throws EnvoyException { public void initializeDBFile() throws EnvoyException {
if (user == null) throw new NullPointerException("Client user is null"); if (user == null) throw new NullPointerException("Client user is null");
if (localDBDir.exists() && !localDBDir.isDirectory())
throw new EnvoyException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath()));
usersFile = new File(localDBDir, "users.db");
localDBFile = new File(localDBDir, user.getID() + ".db"); localDBFile = new File(localDBDir, user.getID() + ".db");
try {
loadUsers();
loadChats();
} catch (ClassNotFoundException | IOException e) {
throw new EnvoyException(e);
}
} }
/** /**
@ -99,7 +99,7 @@ public class LocalDB {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void loadUsers() throws ClassNotFoundException, IOException { users = read(usersFile, ArrayList.class); } public void loadUsers() throws ClassNotFoundException, IOException { users = read(usersFile, HashMap.class); }
/** /**
* Loads all chats saved by Envoy for the client user. * Loads all chats saved by Envoy for the client user.
@ -110,7 +110,7 @@ public class LocalDB {
* @since Envoy v0.1-alpha * @since Envoy v0.1-alpha
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void loadChats() throws ClassNotFoundException, IOException { chats = read(localDBFile, ArrayList.class); } public void loadChats() throws ClassNotFoundException, IOException { chats = read(localDBFile, ArrayList.class); }
private <T> T read(File file, Class<T> serializedClass) throws ClassNotFoundException, IOException { private <T> T read(File file, Class<T> serializedClass) throws ClassNotFoundException, IOException {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) { try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
@ -308,12 +308,12 @@ public class LocalDB {
/** /**
* @return the users * @return the users
*/ */
public List<User> getUsers() { return users; } public Map<String, User> getUsers() { return users; }
/** /**
* @param users the users to set * @param users the users to set
*/ */
public void setUsers(List<User> users) { this.users = users; } public void setUsers(Map<String, User> users) { this.users = users; }
/** /**
* @return all saved {@link Chat} objects that list the client user as the * @return all saved {@link Chat} objects that list the client user as the

View File

@ -34,7 +34,6 @@ import envoy.client.Config;
import envoy.client.LocalDB; import envoy.client.LocalDB;
import envoy.client.Settings; import envoy.client.Settings;
import envoy.schema.Message; import envoy.schema.Message;
import envoy.schema.Sync;
import envoy.schema.User; import envoy.schema.User;
/** /**
@ -132,8 +131,7 @@ public class ChatWindow extends JFrame {
@Override @Override
public void keyReleased(KeyEvent e) { public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER if (e.getKeyCode() == KeyEvent.VK_ENTER
&& ((Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0) && ((Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0) || (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
|| (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
postMessage(messageList); postMessage(messageList);
} }
} }
@ -181,8 +179,8 @@ public class ChatWindow extends JFrame {
settingsButton.addActionListener((evt) -> { settingsButton.addActionListener((evt) -> {
try { try {
SettingsScreen.open(); SettingsScreen.open();
changeChatWindowColors(Settings.getInstance().getCurrentTheme()); changeChatWindowColors(Settings.getInstance().getCurrentTheme());
} catch (Exception e) { } catch (Exception e) {
SettingsScreen.open(); SettingsScreen.open();
logger.log(Level.WARNING, "An error occured while opening the settings screen", e); logger.log(Level.WARNING, "An error occured while opening the settings screen", e);
e.printStackTrace(); e.printStackTrace();
@ -235,7 +233,7 @@ public class ChatWindow extends JFrame {
gbc_userList.insets = new Insets(space, space, space, space); gbc_userList.insets = new Insets(space, space, space, space);
changeChatWindowColors(Settings.getInstance().getCurrentTheme()); changeChatWindowColors(Settings.getInstance().getCurrentTheme());
contentPane.add(userList, gbc_userList); contentPane.add(userList, gbc_userList);
contentPane.revalidate(); contentPane.revalidate();
@ -244,7 +242,6 @@ public class ChatWindow extends JFrame {
contentPane.revalidate(); contentPane.revalidate();
} }
/** /**
* Used to immediately reload the ChatWindow when settings were changed. * Used to immediately reload the ChatWindow when settings were changed.
@ -287,18 +284,14 @@ public class ChatWindow extends JFrame {
private void postMessage(JList<Message> messageList) { private void postMessage(JList<Message> messageList) {
if (!client.hasRecipient()) { if (!client.hasRecipient()) {
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE);
"Please select a recipient!",
"Cannot send message",
JOptionPane.INFORMATION_MESSAGE);
return; return;
} }
if (!messageEnterTextArea.getText().isEmpty()) try { if (!messageEnterTextArea.getText().isEmpty()) try {
// Create and send message object // Create and send message object
final Message message = localDB.createMessage(messageEnterTextArea.getText(), final Message message = localDB.createMessage(messageEnterTextArea.getText(), currentChat.getRecipient().getID());
currentChat.getRecipient().getID());
currentChat.appendMessage(message); currentChat.appendMessage(message);
messageList.setModel(currentChat.getModel()); messageList.setModel(currentChat.getModel());
@ -322,9 +315,8 @@ public class ChatWindow extends JFrame {
*/ */
private void loadUsersAndChats() { private void loadUsersAndChats() {
new Thread(() -> { new Thread(() -> {
Sync users = client.getUsersListXml(); DefaultListModel<User> userListModel = new DefaultListModel<>();
DefaultListModel<User> userListModel = new DefaultListModel<>(); localDB.getUsers().values().forEach(user -> {
users.getUsers().forEach(user -> {
userListModel.addElement(user); userListModel.addElement(user);
// Check if user exists in local DB // Check if user exists in local DB
@ -348,8 +340,7 @@ public class ChatWindow extends JFrame {
new Thread(() -> { new Thread(() -> {
// Synchronize // Synchronize
localDB.applySync( localDB.applySync(client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
// Process unread messages // Process unread messages
localDB.addUnreadMessagesToLocalDB(); localDB.addUnreadMessagesToLocalDB();
@ -359,8 +350,7 @@ public class ChatWindow extends JFrame {
readCurrentChat(); readCurrentChat();
// Update UI // Update UI
SwingUtilities SwingUtilities.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
}).start(); }).start();
}).start(); }).start();
} }
@ -377,4 +367,3 @@ public class ChatWindow extends JFrame {
*/ */
private void readCurrentChat() { if (currentChat != null) { localDB.setMessagesToRead(currentChat); } } private void readCurrentChat() { if (currentChat != null) { localDB.setMessagesToRead(currentChat); } }
} }

View File

@ -1,6 +1,7 @@
package envoy.client.ui; package envoy.client.ui;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.io.IOException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -10,6 +11,7 @@ import envoy.client.Client;
import envoy.client.Config; import envoy.client.Config;
import envoy.client.LocalDB; import envoy.client.LocalDB;
import envoy.exception.EnvoyException; import envoy.exception.EnvoyException;
import envoy.schema.User;
/** /**
* Starts the Envoy client and prompts the user to enter their name. * Starts the Envoy client and prompts the user to enter their name.
@ -54,32 +56,61 @@ 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);
} }
// Acquire the client user (with ID) either from the server or from the local // Initialize the local database
// database, which triggers offline mode LocalDB localDB;
Client client;
try { try {
client = new Client(config, userName); localDB = new LocalDB(config.getLocalDB());
} catch (Exception e1) { } catch (IOException e3) {
logger.log(Level.SEVERE, "Failed to acquire client user ID", e1); logger.log(Level.SEVERE, "Could not initialize local database", e3);
JOptionPane.showMessageDialog(null, e1.toString(), "Client error", JOptionPane.ERROR_MESSAGE); JOptionPane.showMessageDialog(null, "Could not initialize local database!\n" + e3.toString());
System.exit(1); System.exit(1);
return; return;
} }
// Load the local database // Acquire the client user (with ID) either from the server or from the local
LocalDB localDB = new LocalDB(); // database, which triggers offline mode
localDB.setUser(client.getSender()); Client client = new Client(config);
try { try {
localDB.initializeDBFile(config.getLocalDB()); // Try entering online mode first
} catch (EnvoyException e) { 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 | ClassNotFoundException | IOException 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 will 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
if(client.isOnline())
localDB.setUsers(client.getUsers());
EventQueue.invokeLater(() -> { EventQueue.invokeLater(() -> {
try { try {
ChatWindow chatWindow = new ChatWindow(client, localDB); ChatWindow chatWindow = new ChatWindow(client, localDB);