Implemented offline mode for Client and LocalDB
This commit is contained in:
		@@ -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;
 | 
			
		||||
@@ -32,13 +33,11 @@ public class Client {
 | 
			
		||||
	private User			sender, recipient;
 | 
			
		||||
	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 {
 | 
			
		||||
		this.config	= config;
 | 
			
		||||
		sender		= getUser(userName);
 | 
			
		||||
		
 | 
			
		||||
		logger.info("Client user ID: " + sender.getID());
 | 
			
		||||
	public void onlineInit(String userName) throws EnvoyException {
 | 
			
		||||
		sender	= getUser(userName);
 | 
			
		||||
		online	= true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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.
 | 
			
		||||
	 * @since Envoy v0.1-alpha
 | 
			
		||||
	 * @since Envoy v0.2-alpha
 | 
			
		||||
	 */
 | 
			
		||||
	public Sync getUsersListXml() {
 | 
			
		||||
	public Map<String, User> 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<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 void setSender(User sender) { this.sender = sender; }
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the current recipient of the current chat.
 | 
			
		||||
	 * @since Envoy v0.1-alpha
 | 
			
		||||
 
 | 
			
		||||
@@ -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,9 @@ import envoy.schema.User;
 | 
			
		||||
 */
 | 
			
		||||
public class LocalDB {
 | 
			
		||||
 | 
			
		||||
	private File		localDBFile, usersFile;
 | 
			
		||||
	private File		localDBDir, localDBFile, usersFile;
 | 
			
		||||
	private User		user;
 | 
			
		||||
	private List<User>	users	= new ArrayList<>();
 | 
			
		||||
	private Map<String, User>	users	= new HashMap<>();
 | 
			
		||||
	private List<Chat>	chats	= new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
	private ObjectFactory	objectFactory	= new ObjectFactory();
 | 
			
		||||
@@ -54,12 +56,19 @@ public class LocalDB {
 | 
			
		||||
	 *
 | 
			
		||||
	 * @since Envoy v0.1-alpha
 | 
			
		||||
	 */
 | 
			
		||||
	public LocalDB() {
 | 
			
		||||
	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");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -70,18 +79,9 @@ public class LocalDB {
 | 
			
		||||
	 * @throws EnvoyException if the directory selected is not an actual directory.
 | 
			
		||||
	 * @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 (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");
 | 
			
		||||
		try {
 | 
			
		||||
			loadUsers();
 | 
			
		||||
			loadChats();
 | 
			
		||||
		} catch (ClassNotFoundException | IOException e) {
 | 
			
		||||
			throw new EnvoyException(e);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -99,7 +99,7 @@ public class LocalDB {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@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.
 | 
			
		||||
@@ -110,7 +110,7 @@ public class LocalDB {
 | 
			
		||||
	 * @since Envoy v0.1-alpha
 | 
			
		||||
	 */
 | 
			
		||||
	@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 {
 | 
			
		||||
		try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
 | 
			
		||||
@@ -308,12 +308,12 @@ public class LocalDB {
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return the users
 | 
			
		||||
	 */
 | 
			
		||||
	public List<User> getUsers() { return users; }
 | 
			
		||||
	public Map<String, User> getUsers() { return users; }
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @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
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -132,8 +131,7 @@ public class ChatWindow extends JFrame {
 | 
			
		||||
			@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);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -181,8 +179,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();
 | 
			
		||||
@@ -235,7 +233,7 @@ 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();
 | 
			
		||||
 | 
			
		||||
@@ -244,7 +242,6 @@ public class ChatWindow extends JFrame {
 | 
			
		||||
 | 
			
		||||
		contentPane.revalidate();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 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) {
 | 
			
		||||
		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());
 | 
			
		||||
 | 
			
		||||
@@ -322,9 +315,8 @@ public class ChatWindow extends JFrame {
 | 
			
		||||
	 */
 | 
			
		||||
	private void loadUsersAndChats() {
 | 
			
		||||
		new Thread(() -> {
 | 
			
		||||
			Sync					users			= client.getUsersListXml();
 | 
			
		||||
			DefaultListModel<User>	userListModel	= new DefaultListModel<>();
 | 
			
		||||
			users.getUsers().forEach(user -> {
 | 
			
		||||
			DefaultListModel<User> userListModel = new DefaultListModel<>();
 | 
			
		||||
			localDB.getUsers().values().forEach(user -> {
 | 
			
		||||
				userListModel.addElement(user);
 | 
			
		||||
 | 
			
		||||
				// Check if user exists in local DB
 | 
			
		||||
@@ -348,8 +340,7 @@ public class ChatWindow extends JFrame {
 | 
			
		||||
			new Thread(() -> {
 | 
			
		||||
 | 
			
		||||
				// Synchronize
 | 
			
		||||
				localDB.applySync(
 | 
			
		||||
						client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
 | 
			
		||||
				localDB.applySync(client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
 | 
			
		||||
 | 
			
		||||
				// Process unread messages
 | 
			
		||||
				localDB.addUnreadMessagesToLocalDB();
 | 
			
		||||
@@ -359,8 +350,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();
 | 
			
		||||
	}
 | 
			
		||||
@@ -377,4 +367,3 @@ public class ChatWindow extends JFrame {
 | 
			
		||||
	 */
 | 
			
		||||
	private void readCurrentChat() { if (currentChat != null) { localDB.setMessagesToRead(currentChat); } }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package envoy.client.ui;
 | 
			
		||||
 | 
			
		||||
import java.awt.EventQueue;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
 | 
			
		||||
@@ -10,6 +11,7 @@ import envoy.client.Client;
 | 
			
		||||
import envoy.client.Config;
 | 
			
		||||
import envoy.client.LocalDB;
 | 
			
		||||
import envoy.exception.EnvoyException;
 | 
			
		||||
import envoy.schema.User;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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...");
 | 
			
		||||
			System.exit(1);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Acquire the client user (with ID) either from the server or from the local
 | 
			
		||||
		// database, which triggers offline mode
 | 
			
		||||
		Client client;
 | 
			
		||||
		
 | 
			
		||||
		// Initialize the local database
 | 
			
		||||
		LocalDB localDB;
 | 
			
		||||
		try {
 | 
			
		||||
			client = new Client(config, userName);
 | 
			
		||||
		} catch (Exception e1) {
 | 
			
		||||
			logger.log(Level.SEVERE, "Failed to acquire client user ID", e1);
 | 
			
		||||
			JOptionPane.showMessageDialog(null, e1.toString(), "Client error", JOptionPane.ERROR_MESSAGE);
 | 
			
		||||
			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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Load the local database
 | 
			
		||||
		LocalDB localDB = new LocalDB();
 | 
			
		||||
		localDB.setUser(client.getSender());
 | 
			
		||||
		// 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 {
 | 
			
		||||
			localDB.initializeDBFile(config.getLocalDB());
 | 
			
		||||
		} catch (EnvoyException e) {
 | 
			
		||||
			// 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 | ClassNotFoundException | IOException 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);
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		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);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user