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