Merge pull request #99 from informatik-ag-ngl/f/message_synchronization
Fully implemented online message synchronization
This commit is contained in:
commit
e68f15b184
@ -1,9 +1,7 @@
|
|||||||
package envoy.client;
|
package envoy.client;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import envoy.exception.EnvoyException;
|
import envoy.exception.EnvoyException;
|
||||||
@ -31,6 +29,7 @@ public class Config {
|
|||||||
items.put("server", new ConfigItem<>("server", "s", (input) -> input, null));
|
items.put("server", new ConfigItem<>("server", "s", (input) -> input, null));
|
||||||
items.put("port", new ConfigItem<>("port", "p", (input) -> Integer.parseInt(input), null));
|
items.put("port", new ConfigItem<>("port", "p", (input) -> Integer.parseInt(input), null));
|
||||||
items.put("localDB", new ConfigItem<>("localDB", "db", (input) -> new File(input), new File("localDB")));
|
items.put("localDB", new ConfigItem<>("localDB", "db", (input) -> new File(input), new File("localDB")));
|
||||||
|
items.put("ignoreLocalDB", new ConfigItem<>("ignoreLocalDB", "nodb", (input) -> Boolean.parseBoolean(input), false));
|
||||||
items.put("homeDirectory",
|
items.put("homeDirectory",
|
||||||
new ConfigItem<>("homeDirectory", "h", (input) -> new File(input), new File(System.getProperty("user.home"), ".envoy")));
|
new ConfigItem<>("homeDirectory", "h", (input) -> new File(input), new File(System.getProperty("user.home"), ".envoy")));
|
||||||
items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", (input) -> Level.parse(input), Level.CONFIG));
|
items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", (input) -> Level.parse(input), Level.CONFIG));
|
||||||
@ -99,7 +98,7 @@ public class Config {
|
|||||||
* @return {@code true} if server, port and localDB directory are known.
|
* @return {@code true} if server, port and localDB directory are known.
|
||||||
* @since Envoy v0.1-alpha
|
* @since Envoy v0.1-alpha
|
||||||
*/
|
*/
|
||||||
public boolean isInitialized() { return items.values().stream().noneMatch(item -> item.get() == null); }
|
public boolean isInitialized() { return items.values().stream().map(ConfigItem::get).noneMatch(Objects::isNull); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the host name of the Envoy server
|
* @return the host name of the Envoy server
|
||||||
@ -119,6 +118,12 @@ public class Config {
|
|||||||
*/
|
*/
|
||||||
public File getLocalDB() { return (File) items.get("localDB").get(); }
|
public File getLocalDB() { return (File) items.get("localDB").get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the local database is to be ignored
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public Boolean isIgnoreLocalDB() { return (Boolean) items.get("ignoreLocalDB").get(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the directory in which all local files are saves
|
* @return the directory in which all local files are saves
|
||||||
* @since Envoy v0.2-alpha
|
* @since Envoy v0.2-alpha
|
||||||
|
@ -1,163 +0,0 @@
|
|||||||
package envoy.client;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import envoy.data.IdGenerator;
|
|
||||||
import envoy.data.User;
|
|
||||||
import envoy.util.SerializationUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stored information about the current {@link User} and their {@link Chat}s.
|
|
||||||
* For message ID generation a {@link IdGenerator} is stored as well.
|
|
||||||
* These object are persisted inside a folder of the local file system.<br>
|
|
||||||
* <br>
|
|
||||||
* Project: <strong>envoy-client</strong><br>
|
|
||||||
* File: <strong>LocalDB.java</strong><br>
|
|
||||||
* Created: <strong>27.10.2019</strong><br>
|
|
||||||
*
|
|
||||||
* @author Kai S. K. Engelbart
|
|
||||||
* @author Maximilian Käfer
|
|
||||||
* @since Envoy v0.1-alpha
|
|
||||||
*/
|
|
||||||
public class LocalDB {
|
|
||||||
|
|
||||||
private File localDBDir, localDBFile, usersFile, idGeneratorFile;
|
|
||||||
private User user;
|
|
||||||
private Map<String, User> users = new HashMap<>();
|
|
||||||
private List<Chat> chats = new ArrayList<>();
|
|
||||||
private IdGenerator idGenerator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an empty local database. To serialize any chats to the file
|
|
||||||
* system, call {@link LocalDB#initializeDBFile()}.
|
|
||||||
*
|
|
||||||
* @param localDBDir the directory in which to store users and chats
|
|
||||||
* @throws IOException if the LocalDB could not be initialized
|
|
||||||
* @since Envoy v0.1-alpha
|
|
||||||
*/
|
|
||||||
public LocalDB(File localDBDir) throws IOException {
|
|
||||||
this.localDBDir = localDBDir;
|
|
||||||
|
|
||||||
// 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");
|
|
||||||
idGeneratorFile = new File(localDBDir, "id_generator.db");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a database file for a user-specific list of chats.
|
|
||||||
*
|
|
||||||
* @throws NullPointerException if the client user is not yet specified
|
|
||||||
* @since Envoy v0.1-alpha
|
|
||||||
*/
|
|
||||||
public void initializeDBFile() {
|
|
||||||
if (user == null) throw new NullPointerException("Client user is null");
|
|
||||||
localDBFile = new File(localDBDir, user.getId() + ".db");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores all users to the local database. If the client user is specified, the
|
|
||||||
* chats related to this user are stored as well. The message id generator will
|
|
||||||
* also be saved if present.
|
|
||||||
*
|
|
||||||
* @throws IOException if something went wrong during saving
|
|
||||||
* @since Envoy v0.1-alpha
|
|
||||||
*/
|
|
||||||
public void save() throws IOException {
|
|
||||||
// Save users
|
|
||||||
SerializationUtils.write(usersFile, users);
|
|
||||||
|
|
||||||
// Save chats
|
|
||||||
if (user != null) SerializationUtils.write(localDBFile, chats);
|
|
||||||
|
|
||||||
// Save id generator
|
|
||||||
if (hasIdGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads all users that are stored in the local database.
|
|
||||||
*
|
|
||||||
* @throws IOException if the loading process failed
|
|
||||||
* @throws ClassNotFoundException if the loading process failed
|
|
||||||
* @since Envoy v0.2-alpha
|
|
||||||
*/
|
|
||||||
public void loadUsers() throws ClassNotFoundException, IOException { users = SerializationUtils.read(usersFile, HashMap.class); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads all chats saved by Envoy for the client user.
|
|
||||||
*
|
|
||||||
* @throws IOException if the loading process failed
|
|
||||||
* @throws ClassNotFoundException if the loading process failed
|
|
||||||
* @since Envoy v0.1-alpha
|
|
||||||
*/
|
|
||||||
public void loadChats() throws ClassNotFoundException, IOException { chats = SerializationUtils.read(localDBFile, ArrayList.class); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the message ID generator that is stored in the local database. If the
|
|
||||||
* file is not found, the exception is ignored.
|
|
||||||
*
|
|
||||||
* @since Envoy v0.3-alpha
|
|
||||||
*/
|
|
||||||
public void loadIdGenerator() {
|
|
||||||
try {
|
|
||||||
idGenerator = SerializationUtils.read(idGeneratorFile, IdGenerator.class);
|
|
||||||
} catch (ClassNotFoundException | IOException e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a {@code Map<String, User>} of all users stored locally with their
|
|
||||||
* user names as keys
|
|
||||||
* @since Envoy v0.2-alpha
|
|
||||||
*/
|
|
||||||
public Map<String, User> getUsers() { return users; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param users the users to set
|
|
||||||
*/
|
|
||||||
public void setUsers(Map<String, User> users) { this.users = users; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return all saved {@link Chat} objects that list the client user as the
|
|
||||||
* sender
|
|
||||||
* @since Envoy v0.1-alpha
|
|
||||||
**/
|
|
||||||
public List<Chat> getChats() { return chats; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param chats the chats to set
|
|
||||||
*/
|
|
||||||
public void setChats(List<Chat> 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; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the message ID generator
|
|
||||||
* @since Envoy v0.3-alpha
|
|
||||||
*/
|
|
||||||
public IdGenerator getIdGenerator() { return idGenerator; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param idGenerator the message ID generator to set
|
|
||||||
* @since Envoy v0.3-alpha
|
|
||||||
*/
|
|
||||||
public void setIdGenerator(IdGenerator idGenerator) { this.idGenerator = idGenerator; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {@code true} if an {@link IdGenerator} is present
|
|
||||||
* @since Envoy v0.3-alpha
|
|
||||||
*/
|
|
||||||
public boolean hasIdGenerator() { return idGenerator != null; }
|
|
||||||
}
|
|
@ -1,11 +1,14 @@
|
|||||||
package envoy.client;
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import envoy.client.net.Client;
|
||||||
import envoy.client.ui.list.ComponentListModel;
|
import envoy.client.ui.list.ComponentListModel;
|
||||||
import envoy.data.Message;
|
import envoy.data.Message;
|
||||||
import envoy.data.Message.MessageStatus;
|
import envoy.data.Message.MessageStatus;
|
||||||
import envoy.data.User;
|
import envoy.data.User;
|
||||||
|
import envoy.event.MessageStatusChangeEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a chat between two {@link User}s <br>
|
* Represents a chat between two {@link User}s <br>
|
||||||
@ -24,8 +27,8 @@ public class Chat implements Serializable {
|
|||||||
|
|
||||||
private static final long serialVersionUID = -7751248474547242056L;
|
private static final long serialVersionUID = -7751248474547242056L;
|
||||||
|
|
||||||
private User recipient;
|
private final User recipient;
|
||||||
private ComponentListModel<Message> model = new ComponentListModel<>();
|
private final ComponentListModel<Message> model = new ComponentListModel<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the list of messages that the recipient receives.<br>
|
* Provides the list of messages that the recipient receives.<br>
|
||||||
@ -45,16 +48,37 @@ public class Chat implements Serializable {
|
|||||||
public void appendMessage(Message message) { model.add(message); }
|
public void appendMessage(Message message) { model.add(message); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the status of all chat messages to {@code READ} starting from the bottom
|
* Sets the status of all chat messages received from the recipient to
|
||||||
* and stopping once a read message is found.
|
* {@code READ} starting from the bottom and stopping once a read message is
|
||||||
|
* found.
|
||||||
*
|
*
|
||||||
|
* @param client the client instance used to notify the server about the message
|
||||||
|
* status changes
|
||||||
|
* @throws IOException if a {@link MessageStatusChangeEvent} could not be
|
||||||
|
* delivered to the server
|
||||||
* @since Envoy v0.3-alpha
|
* @since Envoy v0.3-alpha
|
||||||
*/
|
*/
|
||||||
public void read() {
|
public void read(Client client) throws IOException {
|
||||||
for (int i = model.size() - 1; i >= 0; --i)
|
for (int i = model.size() - 1; i >= 0; --i) {
|
||||||
if (model.get(i).getStatus() == MessageStatus.READ) break;
|
final Message m = model.get(i);
|
||||||
else model.get(i).setStatus(MessageStatus.READ);
|
if (m.getSenderId() == recipient.getId()) {
|
||||||
|
if (m.getStatus() == MessageStatus.READ) break;
|
||||||
|
else {
|
||||||
|
m.setStatus(MessageStatus.READ);
|
||||||
|
|
||||||
|
// TODO: Cache events in offline mode
|
||||||
|
client.sendEvent(new MessageStatusChangeEvent(m));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the newest message received in the chat doesn't have
|
||||||
|
* the status {@code READ}
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public boolean isUnread() { return !model.isEmpty() && model.get(model.size() - 1).getStatus() != MessageStatus.READ; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return all messages in the current chat
|
* @return all messages in the current chat
|
118
src/main/java/envoy/client/data/LocalDb.java
Normal file
118
src/main/java/envoy/client/data/LocalDb.java
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import envoy.data.IdGenerator;
|
||||||
|
import envoy.data.User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores information about the current {@link User} and their {@link Chat}s.
|
||||||
|
* For message ID generation a {@link IdGenerator} is stored as well.<br>
|
||||||
|
* <br>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>LocalDb.java</strong><br>
|
||||||
|
* Created: <strong>3 Feb 2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public abstract class LocalDb {
|
||||||
|
|
||||||
|
protected User user;
|
||||||
|
protected Map<String, User> users = new HashMap<>();
|
||||||
|
protected List<Chat> chats = new ArrayList<>();
|
||||||
|
protected IdGenerator idGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a storage space for a user-specific list of chats.
|
||||||
|
*
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void initializeUserStorage() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores all users. If the client user is specified, their chats will be stored
|
||||||
|
* as well. The message id generator will also be saved if present.
|
||||||
|
*
|
||||||
|
* @throws Exception if the saving process failed
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void save() throws Exception {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all user data.
|
||||||
|
*
|
||||||
|
* @throws Exception if the loading process failed
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void loadUsers() throws Exception {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all chat data of the client user.
|
||||||
|
*
|
||||||
|
* @throws Exception if the loading process failed
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void loadChats() throws Exception {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the ID generator. Any exception thrown during this process is ignored.
|
||||||
|
*
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void loadIdGenerator() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a {@code Map<String, User>} of all users stored locally with their
|
||||||
|
* user names as keys
|
||||||
|
* @since Envoy v0.2-alpha
|
||||||
|
*/
|
||||||
|
public Map<String, User> getUsers() { return users; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param users the users to set
|
||||||
|
*/
|
||||||
|
public void setUsers(Map<String, User> users) { this.users = users; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return all saved {@link Chat} objects that list the client user as the
|
||||||
|
* sender
|
||||||
|
* @since Envoy v0.1-alpha
|
||||||
|
**/
|
||||||
|
public List<Chat> getChats() { return chats; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param chats the chats to set
|
||||||
|
*/
|
||||||
|
public void setChats(List<Chat> 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; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the message ID generator
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public IdGenerator getIdGenerator() { return idGenerator; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param idGenerator the message ID generator to set
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void setIdGenerator(IdGenerator idGenerator) { this.idGenerator = idGenerator; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if an {@link IdGenerator} is present
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public boolean hasIdGenerator() { return idGenerator != null; }
|
||||||
|
}
|
93
src/main/java/envoy/client/data/PersistentLocalDb.java
Normal file
93
src/main/java/envoy/client/data/PersistentLocalDb.java
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import envoy.client.ConfigItem;
|
||||||
|
import envoy.data.IdGenerator;
|
||||||
|
import envoy.util.SerializationUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a {@link LocalDb} in a way that stores all information inside a
|
||||||
|
* folder on the local file system.<br>
|
||||||
|
* <br>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>PersistentLocalDb.java</strong><br>
|
||||||
|
* Created: <strong>27.10.2019</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @author Maximilian Käfer
|
||||||
|
* @since Envoy v0.1-alpha
|
||||||
|
*/
|
||||||
|
public class PersistentLocalDb extends LocalDb {
|
||||||
|
|
||||||
|
private File localDBDir, localDBFile, usersFile, idGeneratorFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes an empty local database without a directory. All changes made to
|
||||||
|
* this instance cannot be saved to the file system.<br>
|
||||||
|
* <br>
|
||||||
|
* This constructor shall be used in conjunction with the {@code ignoreLocalDB}
|
||||||
|
* {@link ConfigItem}.
|
||||||
|
*
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public PersistentLocalDb() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an empty local database. To serialize any chats to the file
|
||||||
|
* system, call {@link PersistentLocalDb#initializeUserStorage()}.
|
||||||
|
*
|
||||||
|
* @param localDBDir the directory in which to store users and chats
|
||||||
|
* @throws IOException if the PersistentLocalDb could not be initialized
|
||||||
|
* @since Envoy v0.1-alpha
|
||||||
|
*/
|
||||||
|
public PersistentLocalDb(File localDBDir) throws IOException {
|
||||||
|
this.localDBDir = localDBDir;
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
idGeneratorFile = new File(localDBDir, "id_generator.db");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a database file for a user-specific list of chats.
|
||||||
|
*
|
||||||
|
* @throws NullPointerException if the client user is not yet specified
|
||||||
|
* @since Envoy v0.1-alpha
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void initializeUserStorage() {
|
||||||
|
if (user == null) throw new NullPointerException("Client user is null");
|
||||||
|
localDBFile = new File(localDBDir, user.getId() + ".db");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save() throws IOException {
|
||||||
|
// Save users
|
||||||
|
SerializationUtils.write(usersFile, users);
|
||||||
|
|
||||||
|
// Save chats
|
||||||
|
if (user != null) SerializationUtils.write(localDBFile, chats);
|
||||||
|
|
||||||
|
// Save id generator
|
||||||
|
if (hasIdGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadUsers() throws ClassNotFoundException, IOException { users = SerializationUtils.read(usersFile, HashMap.class); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadChats() throws ClassNotFoundException, IOException { chats = SerializationUtils.read(localDBFile, ArrayList.class); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadIdGenerator() {
|
||||||
|
try {
|
||||||
|
idGenerator = SerializationUtils.read(idGeneratorFile, IdGenerator.class);
|
||||||
|
} catch (ClassNotFoundException | IOException e) {}
|
||||||
|
}
|
||||||
|
}
|
15
src/main/java/envoy/client/data/TransientLocalDb.java
Normal file
15
src/main/java/envoy/client/data/TransientLocalDb.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package envoy.client.data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a {@link LocalDb} in a way that does not persist any information
|
||||||
|
* after application shutdown.<br>
|
||||||
|
* <br>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>TransientLocalDb.java</strong><br>
|
||||||
|
* Created: <strong>3 Feb 2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public class TransientLocalDb extends LocalDb {
|
||||||
|
}
|
@ -8,9 +8,12 @@ import envoy.data.Message;
|
|||||||
* Created: <strong>4 Dec 2019</strong><br>
|
* Created: <strong>4 Dec 2019</strong><br>
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public class MessageCreationEvent extends MessageEvent {
|
public class MessageCreationEvent extends MessageEvent {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -6451021678064566774L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param message the {@link Message} that has been created
|
* @param message the {@link Message} that has been created
|
||||||
*/
|
*/
|
||||||
|
@ -9,9 +9,12 @@ import envoy.event.Event;
|
|||||||
* Created: <strong>4 Dec 2019</strong><br>
|
* Created: <strong>4 Dec 2019</strong><br>
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public class MessageEvent implements Event<Message> {
|
public class MessageEvent implements Event<Message> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 7658989461923112804L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the {@link Message} attached to this {@link MessageEvent}.
|
* the {@link Message} attached to this {@link MessageEvent}.
|
||||||
*/
|
*/
|
||||||
|
@ -8,9 +8,12 @@ import envoy.data.Message;
|
|||||||
* Created: <strong>4 Dec 2019</strong><br>
|
* Created: <strong>4 Dec 2019</strong><br>
|
||||||
*
|
*
|
||||||
* @author Kai S. K. Engelbart
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public class MessageModificationEvent extends MessageEvent {
|
public class MessageModificationEvent extends MessageEvent {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 4650039506439563116L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param message the {@link Message} that has been modified
|
* @param message the {@link Message} that has been modified
|
||||||
*/
|
*/
|
||||||
|
@ -13,6 +13,7 @@ import envoy.event.Event;
|
|||||||
*/
|
*/
|
||||||
public class ThemeChangeEvent implements Event<Theme> {
|
public class ThemeChangeEvent implements Event<Theme> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 6756772448803774547L;
|
||||||
private final Theme theme;
|
private final Theme theme;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package envoy.client;
|
package envoy.client.net;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -9,12 +9,19 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import javax.naming.TimeLimitExceededException;
|
import javax.naming.TimeLimitExceededException;
|
||||||
|
|
||||||
|
import envoy.client.Config;
|
||||||
|
import envoy.client.data.LocalDb;
|
||||||
import envoy.client.util.EnvoyLog;
|
import envoy.client.util.EnvoyLog;
|
||||||
import envoy.data.*;
|
import envoy.data.*;
|
||||||
|
import envoy.event.Event;
|
||||||
import envoy.event.IdGeneratorRequest;
|
import envoy.event.IdGeneratorRequest;
|
||||||
|
import envoy.event.MessageStatusChangeEvent;
|
||||||
import envoy.util.SerializationUtils;
|
import envoy.util.SerializationUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Establishes a connection to the server, performs a handshake and delivers
|
||||||
|
* certain objects to the server.<br>
|
||||||
|
* <br>
|
||||||
* Project: <strong>envoy-client</strong><br>
|
* Project: <strong>envoy-client</strong><br>
|
||||||
* File: <strong>Client.java</strong><br>
|
* File: <strong>Client.java</strong><br>
|
||||||
* Created: <strong>28 Sep 2019</strong><br>
|
* Created: <strong>28 Sep 2019</strong><br>
|
||||||
@ -26,17 +33,17 @@ import envoy.util.SerializationUtils;
|
|||||||
*/
|
*/
|
||||||
public class Client implements Closeable {
|
public class Client implements Closeable {
|
||||||
|
|
||||||
|
// Connection handling
|
||||||
private Socket socket;
|
private Socket socket;
|
||||||
private Receiver receiver;
|
private Receiver receiver;
|
||||||
private boolean online;
|
private boolean online;
|
||||||
|
|
||||||
|
// Asynchronously initialized during handshake
|
||||||
private volatile User sender;
|
private volatile User sender;
|
||||||
private User recipient;
|
|
||||||
|
|
||||||
private volatile Contacts contacts;
|
private volatile Contacts contacts;
|
||||||
|
|
||||||
private Config config = Config.getInstance();
|
// Configuration and logging
|
||||||
|
private static final Config config = Config.getInstance();
|
||||||
private static final Logger logger = EnvoyLog.getLogger(Client.class.getSimpleName());
|
private static final Logger logger = EnvoyLog.getLogger(Client.class.getSimpleName());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,13 +53,15 @@ public class Client implements Closeable {
|
|||||||
* an exception is thrown.
|
* an exception is thrown.
|
||||||
*
|
*
|
||||||
* @param credentials the login credentials of the user
|
* @param credentials the login credentials of the user
|
||||||
* @param localDB the local database used to persist the current
|
* @param localDb the local database used to persist the current
|
||||||
* {@link IdGenerator}
|
* {@link IdGenerator}
|
||||||
|
* @return a message cache containing all unread messages from the server that
|
||||||
|
* can be relayed after initialization
|
||||||
* @throws Exception if the online mode could not be entered or the request
|
* @throws Exception if the online mode could not be entered or the request
|
||||||
* failed for some other reason
|
* failed for some other reason
|
||||||
* @since Envoy v0.2-alpha
|
* @since Envoy v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public void onlineInit(LoginCredentials credentials, LocalDB localDB) throws Exception {
|
public MessageCache onlineInit(LoginCredentials credentials, LocalDb localDb) throws Exception {
|
||||||
// Establish TCP connection
|
// Establish TCP connection
|
||||||
logger.info(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
|
logger.info(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
|
||||||
socket = new Socket(config.getServer(), config.getPort());
|
socket = new Socket(config.getServer(), config.getPort());
|
||||||
@ -61,9 +70,13 @@ public class Client implements Closeable {
|
|||||||
// Create message receiver
|
// Create message receiver
|
||||||
receiver = new Receiver(socket.getInputStream());
|
receiver = new Receiver(socket.getInputStream());
|
||||||
|
|
||||||
// Register user creation processor
|
// Create cache for unread messages
|
||||||
|
final MessageCache cache = new MessageCache();
|
||||||
|
|
||||||
|
// Register user creation processor, contact list processor and message cache
|
||||||
receiver.registerProcessor(User.class, sender -> { logger.info("Acquired user object " + sender); this.sender = sender; });
|
receiver.registerProcessor(User.class, sender -> { logger.info("Acquired user object " + sender); this.sender = sender; });
|
||||||
receiver.registerProcessor(Contacts.class, contacts -> { logger.info("Acquired contacts object " + contacts); this.contacts = contacts; });
|
receiver.registerProcessor(Contacts.class, contacts -> { logger.info("Acquired contacts object " + contacts); this.contacts = contacts; });
|
||||||
|
receiver.registerProcessor(Message.class, cache);
|
||||||
|
|
||||||
// Start receiver
|
// Start receiver
|
||||||
new Thread(receiver).start();
|
new Thread(receiver).start();
|
||||||
@ -86,18 +99,27 @@ public class Client implements Closeable {
|
|||||||
receiver.removeAllProcessors();
|
receiver.removeAllProcessors();
|
||||||
|
|
||||||
// Register processors for message and status handling
|
// Register processors for message and status handling
|
||||||
receiver.registerProcessor(Message.class, new ReceivedMessageProcessor());
|
final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor();
|
||||||
// TODO: Status handling
|
receiver.registerProcessor(Message.class, receivedMessageProcessor);
|
||||||
|
|
||||||
|
// Relay cached unread messages
|
||||||
|
cache.setProcessor(receivedMessageProcessor);
|
||||||
|
|
||||||
|
// Process message status changes
|
||||||
|
receiver.registerProcessor(MessageStatusChangeEvent.class, new MessageStatusChangeEventProcessor());
|
||||||
|
|
||||||
// Process message ID generation
|
// Process message ID generation
|
||||||
receiver.registerProcessor(IdGenerator.class, localDB::setIdGenerator);
|
receiver.registerProcessor(IdGenerator.class, localDb::setIdGenerator);
|
||||||
|
|
||||||
// Request a generator if none is present
|
// Request a generator if none is present or the existing one is consumed
|
||||||
if (!localDB.hasIdGenerator() || !localDB.getIdGenerator().hasNext()) requestIdGenerator();
|
if (!localDb.hasIdGenerator() || !localDb.getIdGenerator().hasNext()) requestIdGenerator();
|
||||||
|
|
||||||
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a message to the server.
|
* Sends a message to the server. The message's status will be incremented once
|
||||||
|
* it was delivered successfully.
|
||||||
*
|
*
|
||||||
* @param message the message to send
|
* @param message the message to send
|
||||||
* @throws IOException if the message does not reach the server
|
* @throws IOException if the message does not reach the server
|
||||||
@ -108,6 +130,14 @@ public class Client implements Closeable {
|
|||||||
message.nextStatus();
|
message.nextStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an event to the server.
|
||||||
|
*
|
||||||
|
* @param evt the event to send
|
||||||
|
* @throws IOException if the event did not reach the server
|
||||||
|
*/
|
||||||
|
public void sendEvent(Event<?> evt) throws IOException { writeObject(evt); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests a new {@link IdGenerator} from the server.
|
* Requests a new {@link IdGenerator} from the server.
|
||||||
*
|
*
|
||||||
@ -155,26 +185,6 @@ public class Client implements Closeable {
|
|||||||
*/
|
*/
|
||||||
public void setSender(User sender) { this.sender = sender; }
|
public void setSender(User sender) { this.sender = 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; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the {@link Receiver} used by this {@link Client}
|
* @return the {@link Receiver} used by this {@link Client}
|
||||||
*/
|
*/
|
54
src/main/java/envoy/client/net/MessageCache.java
Normal file
54
src/main/java/envoy/client/net/MessageCache.java
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package envoy.client.net;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import envoy.client.util.EnvoyLog;
|
||||||
|
import envoy.data.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores messages in a queue until the application initialization is complete.
|
||||||
|
* The messages can then be relayed to a processor.<br>
|
||||||
|
* <br>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>MessageCache.java</strong><br>
|
||||||
|
* Created: <strong>4 Feb 2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public class MessageCache implements Consumer<Message> {
|
||||||
|
|
||||||
|
private final Queue<Message> messages = new LinkedList<>();
|
||||||
|
private Consumer<Message> processor;
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(MessageCache.class.getSimpleName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a message to the cache.
|
||||||
|
*
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void accept(Message message) {
|
||||||
|
logger.info(String.format("Adding message %s to cache", message));
|
||||||
|
messages.add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the processor to which messages are relayed.
|
||||||
|
*
|
||||||
|
* @param processor the processor to set
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void setProcessor(Consumer<Message> processor) { this.processor = processor; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relays all cached messages to the processor.
|
||||||
|
*
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void relayMessages() { messages.forEach(processor::accept); }
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package envoy.client.net;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import envoy.client.util.EnvoyLog;
|
||||||
|
import envoy.data.Message.MessageStatus;
|
||||||
|
import envoy.event.EventBus;
|
||||||
|
import envoy.event.MessageStatusChangeEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>MessageStatusChangeEventProcessor.java</strong><br>
|
||||||
|
* Created: <strong>4 Feb 2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public class MessageStatusChangeEventProcessor implements Consumer<MessageStatusChangeEvent> {
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(MessageStatusChangeEventProcessor.class.getSimpleName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a {@link MessageStatusChangeEvent} if the status is
|
||||||
|
* {@code RECEIVED} or {@code READ}.
|
||||||
|
*
|
||||||
|
* @param evt the status change event
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void accept(MessageStatusChangeEvent evt) {
|
||||||
|
if (evt.get().ordinal() < MessageStatus.RECEIVED.ordinal()) logger.info("Received invalid message status change " + evt);
|
||||||
|
else {
|
||||||
|
logger.info("Received " + evt.toString());
|
||||||
|
EventBus.getInstance().dispatch(evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package envoy.client;
|
package envoy.client.net;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@ -25,8 +25,12 @@ public class ReceivedMessageProcessor implements Consumer<Message> {
|
|||||||
public void accept(Message message) {
|
public void accept(Message message) {
|
||||||
logger.info("Received message object " + message);
|
logger.info("Received message object " + message);
|
||||||
if (message.getStatus() != MessageStatus.SENT) logger.warning("The message has the unexpected status " + message.getStatus());
|
if (message.getStatus() != MessageStatus.SENT) logger.warning("The message has the unexpected status " + message.getStatus());
|
||||||
else
|
else {
|
||||||
|
// Update status to RECEIVED
|
||||||
|
message.nextStatus();
|
||||||
|
|
||||||
// Dispatch event
|
// Dispatch event
|
||||||
EventBus.getInstance().dispatch(new MessageCreationEvent(message));
|
EventBus.getInstance().dispatch(new MessageCreationEvent(message));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package envoy.client;
|
package envoy.client.net;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
@ -3,22 +3,28 @@ package envoy.client.ui;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.KeyAdapter;
|
import java.awt.event.KeyAdapter;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
|
|
||||||
import envoy.client.*;
|
import envoy.client.Settings;
|
||||||
|
import envoy.client.data.Chat;
|
||||||
|
import envoy.client.data.LocalDb;
|
||||||
import envoy.client.event.MessageCreationEvent;
|
import envoy.client.event.MessageCreationEvent;
|
||||||
import envoy.client.event.ThemeChangeEvent;
|
import envoy.client.event.ThemeChangeEvent;
|
||||||
|
import envoy.client.net.Client;
|
||||||
import envoy.client.ui.list.ComponentList;
|
import envoy.client.ui.list.ComponentList;
|
||||||
import envoy.client.ui.settings.SettingsScreen;
|
import envoy.client.ui.settings.SettingsScreen;
|
||||||
import envoy.client.util.EnvoyLog;
|
import envoy.client.util.EnvoyLog;
|
||||||
import envoy.data.Message;
|
import envoy.data.Message;
|
||||||
|
import envoy.data.Message.MessageStatus;
|
||||||
import envoy.data.MessageBuilder;
|
import envoy.data.MessageBuilder;
|
||||||
import envoy.data.User;
|
import envoy.data.User;
|
||||||
import envoy.event.EventBus;
|
import envoy.event.EventBus;
|
||||||
|
import envoy.event.MessageStatusChangeEvent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Project: <strong>envoy-client</strong><br>
|
* Project: <strong>envoy-client</strong><br>
|
||||||
@ -34,7 +40,7 @@ public class ChatWindow extends JFrame {
|
|||||||
|
|
||||||
// User specific objects
|
// User specific objects
|
||||||
private Client client;
|
private Client client;
|
||||||
private LocalDB localDB;
|
private LocalDb localDb;
|
||||||
|
|
||||||
// GUI components
|
// GUI components
|
||||||
private JPanel contentPane = new JPanel();
|
private JPanel contentPane = new JPanel();
|
||||||
@ -161,25 +167,25 @@ public class ChatWindow extends JFrame {
|
|||||||
userList.setCellRenderer(new UserListRenderer());
|
userList.setCellRenderer(new UserListRenderer());
|
||||||
userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
userList.addListSelectionListener((listSelectionEvent) -> {
|
userList.addListSelectionListener((listSelectionEvent) -> {
|
||||||
if (client != null && localDB != null && !listSelectionEvent.getValueIsAdjusting()) {
|
if (client != null && localDb != null && !listSelectionEvent.getValueIsAdjusting()) {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final JList<User> selectedUserList = (JList<User>) listSelectionEvent.getSource();
|
final JList<User> selectedUserList = (JList<User>) listSelectionEvent.getSource();
|
||||||
final User user = selectedUserList.getSelectedValue();
|
final User user = selectedUserList.getSelectedValue();
|
||||||
|
|
||||||
// Select current chat
|
// Select current chat
|
||||||
currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getId() == user.getId()).findFirst().get();
|
currentChat = localDb.getChats().stream().filter(chat -> chat.getRecipient().getId() == user.getId()).findFirst().get();
|
||||||
|
|
||||||
// Read current Chat
|
// Read current chat
|
||||||
currentChat.read();
|
readCurrentChat();
|
||||||
|
|
||||||
// Set recipient in client and chat title
|
// Set chat title
|
||||||
client.setRecipient(user);
|
|
||||||
textPane.setText(currentChat.getRecipient().getName());
|
textPane.setText(currentChat.getRecipient().getName());
|
||||||
|
|
||||||
// Update model and scroll down
|
// Update model and scroll down
|
||||||
messageList.setModel(currentChat.getModel());
|
messageList.setModel(currentChat.getModel());
|
||||||
scrollPane.setChatOpened(true);
|
scrollPane.setChatOpened(true);
|
||||||
|
|
||||||
|
messageList.synchronizeModel();
|
||||||
revalidate();
|
revalidate();
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
@ -206,7 +212,35 @@ public class ChatWindow extends JFrame {
|
|||||||
// Listen to received messages
|
// Listen to received messages
|
||||||
EventBus.getInstance().register(MessageCreationEvent.class, (evt) -> {
|
EventBus.getInstance().register(MessageCreationEvent.class, (evt) -> {
|
||||||
Message message = ((MessageCreationEvent) evt).get();
|
Message message = ((MessageCreationEvent) evt).get();
|
||||||
localDB.getChats().stream().filter(c -> c.getRecipient().getId() == message.getSenderId()).findFirst().get().appendMessage(message);
|
Chat chat = localDb.getChats().stream().filter(c -> c.getRecipient().getId() == message.getSenderId()).findFirst().get();
|
||||||
|
chat.appendMessage(message);
|
||||||
|
|
||||||
|
// Read message and update UI if in current chat
|
||||||
|
if (chat == currentChat) readCurrentChat();
|
||||||
|
|
||||||
|
revalidate();
|
||||||
|
repaint();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen to message status changes
|
||||||
|
EventBus.getInstance().register(MessageStatusChangeEvent.class, (evt) -> {
|
||||||
|
final long id = ((MessageStatusChangeEvent) evt).getId();
|
||||||
|
final MessageStatus status = (MessageStatus) evt.get();
|
||||||
|
|
||||||
|
for (Chat c : localDb.getChats())
|
||||||
|
for (Message m : c.getModel())
|
||||||
|
if (m.getId() == id) {
|
||||||
|
|
||||||
|
// Update message status
|
||||||
|
m.setStatus(status);
|
||||||
|
|
||||||
|
// Update model and scroll down if current chat
|
||||||
|
if (c == currentChat) {
|
||||||
|
messageList.setModel(currentChat.getModel());
|
||||||
|
scrollPane.setChatOpened(true);
|
||||||
|
} else messageList.synchronizeModel();
|
||||||
|
}
|
||||||
|
|
||||||
revalidate();
|
revalidate();
|
||||||
repaint();
|
repaint();
|
||||||
});
|
});
|
||||||
@ -254,7 +288,7 @@ public class ChatWindow extends JFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void postMessage() {
|
private void postMessage() {
|
||||||
if (!client.hasRecipient()) {
|
if (userList.isSelectionEmpty()) {
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
@ -262,7 +296,7 @@ public class ChatWindow extends JFrame {
|
|||||||
if (!messageEnterTextArea.getText().isEmpty()) try {
|
if (!messageEnterTextArea.getText().isEmpty()) try {
|
||||||
|
|
||||||
// Create message
|
// Create message
|
||||||
final Message message = new MessageBuilder(localDB.getUser().getId(), currentChat.getRecipient().getId(), localDB.getIdGenerator())
|
final Message message = new MessageBuilder(localDb.getUser().getId(), currentChat.getRecipient().getId(), localDb.getIdGenerator())
|
||||||
.setText(messageEnterTextArea.getText())
|
.setText(messageEnterTextArea.getText())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@ -270,9 +304,8 @@ public class ChatWindow extends JFrame {
|
|||||||
// TODO: Store offline messages
|
// TODO: Store offline messages
|
||||||
client.sendMessage(message);
|
client.sendMessage(message);
|
||||||
|
|
||||||
// Add message to LocalDB and update UI
|
// Add message to PersistentLocalDb and update UI
|
||||||
currentChat.appendMessage(message);
|
currentChat.appendMessage(message);
|
||||||
// messageList.setModel(currentChat.getModel());
|
|
||||||
|
|
||||||
// Clear text field
|
// Clear text field
|
||||||
messageEnterTextArea.setText("");
|
messageEnterTextArea.setText("");
|
||||||
@ -281,14 +314,11 @@ public class ChatWindow extends JFrame {
|
|||||||
revalidate();
|
revalidate();
|
||||||
repaint();
|
repaint();
|
||||||
|
|
||||||
// Request a new id generator if all ids were used
|
// Request a new id generator if all IDs were used
|
||||||
if (!localDB.getIdGenerator().hasNext()) client.requestIdGenerator();
|
if (!localDb.getIdGenerator().hasNext()) client.requestIdGenerator();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
JOptionPane.showMessageDialog(this,
|
JOptionPane.showMessageDialog(this, "Error sending message:\n" + e.toString(), "Message sending error", JOptionPane.ERROR_MESSAGE);
|
||||||
"Error sending message:\n" + e.toString(),
|
|
||||||
"Message sending error",
|
|
||||||
JOptionPane.ERROR_MESSAGE);
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -302,36 +332,46 @@ public class ChatWindow extends JFrame {
|
|||||||
private void loadUsersAndChats() {
|
private void loadUsersAndChats() {
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
DefaultListModel<User> userListModel = new DefaultListModel<>();
|
DefaultListModel<User> userListModel = new DefaultListModel<>();
|
||||||
localDB.getUsers().values().forEach(user -> {
|
localDb.getUsers().values().forEach(user -> {
|
||||||
userListModel.addElement(user);
|
userListModel.addElement(user);
|
||||||
|
|
||||||
// Check if user exists in local DB
|
// Check if user exists in local DB
|
||||||
if (localDB.getChats().stream().filter(c -> c.getRecipient().getId() == user.getId()).count() == 0)
|
if (localDb.getChats().stream().filter(c -> c.getRecipient().getId() == user.getId()).count() == 0)
|
||||||
localDB.getChats().add(new Chat(user));
|
localDb.getChats().add(new Chat(user));
|
||||||
});
|
});
|
||||||
SwingUtilities.invokeLater(() -> userList.setModel(userListModel));
|
SwingUtilities.invokeLater(() -> userList.setModel(userListModel));
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void readCurrentChat() {
|
||||||
|
try {
|
||||||
|
currentChat.read(client);
|
||||||
|
messageList.synchronizeModel();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
logger.log(Level.WARNING, "Couldn't notify server about message status change", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link Client} used by this {@link ChatWindow}.
|
* Sets the {@link Client} used by this {@link ChatWindow}.
|
||||||
*
|
*
|
||||||
* @param client the {@link Client} used to send and receive messages
|
* @param client the {@link Client} used to send and receive messages
|
||||||
* @since Envoy v0.2-alpha
|
* @since Envoy v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public void setClient(Client client) {
|
public void setClient(Client client) { this.client = client; }
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link LocalDB} used by this {@link ChatWindow}. After invoking this
|
* Sets the {@link LocalDb} used by this {@link ChatWindow}. After
|
||||||
|
* invoking this
|
||||||
* method, users and chats will be loaded from the database into the GUI.
|
* method, users and chats will be loaded from the database into the GUI.
|
||||||
*
|
*
|
||||||
* @param localDB the {@link LocalDB} used to manage stored messages and users
|
* @param localDb the {@link LocalDb} used to manage stored messages
|
||||||
|
* and users
|
||||||
* @since Envoy v0.2-alpha
|
* @since Envoy v0.2-alpha
|
||||||
*/
|
*/
|
||||||
public void setLocalDB(LocalDB localDB) {
|
public void setLocalDB(LocalDb localDb) {
|
||||||
this.localDB = localDB;
|
this.localDb = localDb;
|
||||||
loadUsersAndChats();
|
loadUsersAndChats();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,13 @@ import javax.swing.JFrame;
|
|||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
import envoy.client.*;
|
import envoy.client.Config;
|
||||||
|
import envoy.client.Settings;
|
||||||
|
import envoy.client.data.LocalDb;
|
||||||
|
import envoy.client.data.PersistentLocalDb;
|
||||||
|
import envoy.client.data.TransientLocalDb;
|
||||||
|
import envoy.client.net.Client;
|
||||||
|
import envoy.client.net.MessageCache;
|
||||||
import envoy.client.util.EnvoyLog;
|
import envoy.client.util.EnvoyLog;
|
||||||
import envoy.data.LoginCredentials;
|
import envoy.data.LoginCredentials;
|
||||||
import envoy.data.User;
|
import envoy.data.User;
|
||||||
@ -63,8 +69,8 @@ public class Startup {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
JOptionPane
|
JOptionPane
|
||||||
.showMessageDialog(null, "Error loading configuration values:\n" + e.toString(), "Configuration error", JOptionPane.ERROR_MESSAGE);
|
.showMessageDialog(null, "Error loading configuration values:\n" + e.toString(), "Configuration error", JOptionPane.ERROR_MESSAGE);
|
||||||
System.exit(1);
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set new logger levels loaded from config
|
// Set new logger levels loaded from config
|
||||||
@ -80,12 +86,19 @@ public class Startup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the local database
|
// Initialize the local database
|
||||||
LocalDB localDB;
|
LocalDb localDb;
|
||||||
try {
|
if (config.isIgnoreLocalDB()) {
|
||||||
localDB = new LocalDB(new File(config.getHomeDirectory(), config.getLocalDB().getPath()));
|
localDb = new TransientLocalDb();
|
||||||
|
JOptionPane.showMessageDialog(null,
|
||||||
|
"Ignoring local database.\nMessages will not be saved!",
|
||||||
|
"Local database warning",
|
||||||
|
JOptionPane.WARNING_MESSAGE);
|
||||||
|
} else try {
|
||||||
|
localDb = new PersistentLocalDb(new File(config.getHomeDirectory(), config.getLocalDB().getPath()));
|
||||||
} catch (IOException e3) {
|
} catch (IOException e3) {
|
||||||
logger.log(Level.SEVERE, "Could not initialize local database", e3);
|
logger.log(Level.SEVERE, "Could not initialize local database", e3);
|
||||||
JOptionPane.showMessageDialog(null, "Could not initialize local database!\n" + e3.toString());
|
JOptionPane
|
||||||
|
.showMessageDialog(null, "Could not initialize local database!\n" + e3.toString(), "Local database error", JOptionPane.ERROR_MESSAGE);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -95,17 +108,18 @@ public class Startup {
|
|||||||
// Acquire the client user (with ID) either from the server or from the local
|
// Acquire the client user (with ID) either from the server or from the local
|
||||||
// database, which triggers offline mode
|
// database, which triggers offline mode
|
||||||
Client client = new Client();
|
Client client = new Client();
|
||||||
|
MessageCache cache = null;
|
||||||
try {
|
try {
|
||||||
// Try entering online mode first
|
// Try entering online mode first
|
||||||
localDB.loadIdGenerator();
|
localDb.loadIdGenerator();
|
||||||
client.onlineInit(credentials, localDB);
|
cache = client.onlineInit(credentials, localDb);
|
||||||
} catch (Exception e1) {
|
} catch (Exception e1) {
|
||||||
logger.warning("Could not connect to server. Trying offline mode...");
|
logger.warning("Could not connect to server. Trying offline mode...");
|
||||||
e1.printStackTrace();
|
e1.printStackTrace();
|
||||||
try {
|
try {
|
||||||
// Try entering offline mode
|
// Try entering offline mode
|
||||||
localDB.loadUsers();
|
localDb.loadUsers();
|
||||||
User clientUser = localDB.getUsers().get(credentials.getName());
|
User clientUser = localDb.getUsers().get(credentials.getName());
|
||||||
if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown");
|
if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown");
|
||||||
client.setSender(clientUser);
|
client.setSender(clientUser);
|
||||||
JOptionPane.showMessageDialog(null,
|
JOptionPane.showMessageDialog(null,
|
||||||
@ -120,12 +134,12 @@ public class Startup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set client user in local database
|
// Set client user in local database
|
||||||
localDB.setUser(client.getSender());
|
localDb.setUser(client.getSender());
|
||||||
|
|
||||||
// Initialize chats in local database
|
// Initialize chats in local database
|
||||||
try {
|
try {
|
||||||
localDB.initializeDBFile();
|
localDb.initializeUserStorage();
|
||||||
localDB.loadChats();
|
localDb.loadChats();
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
// The local database file has not yet been created, probably first login
|
// The local database file has not yet been created, probably first login
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -137,12 +151,13 @@ public class Startup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save all users to the local database
|
// Save all users to the local database
|
||||||
if (client.isOnline()) localDB.setUsers(client.getUsers());
|
if (client.isOnline()) localDb.setUsers(client.getUsers());
|
||||||
|
|
||||||
|
// Display ChatWindow and StatusTrayIcon
|
||||||
EventQueue.invokeLater(() -> {
|
EventQueue.invokeLater(() -> {
|
||||||
try {
|
try {
|
||||||
chatWindow.setClient(client);
|
chatWindow.setClient(client);
|
||||||
chatWindow.setLocalDB(localDB);
|
chatWindow.setLocalDB(localDb);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new StatusTrayIcon(chatWindow).show();
|
new StatusTrayIcon(chatWindow).show();
|
||||||
@ -162,16 +177,19 @@ public class Startup {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save Settings and LocalDB on shutdown
|
// Relay unread messages from cache
|
||||||
|
if (cache != null) cache.relayMessages();
|
||||||
|
|
||||||
|
// Save Settings and PersistentLocalDb on shutdown
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
logger.info("Closing connection...");
|
logger.info("Closing connection...");
|
||||||
client.close();
|
client.close();
|
||||||
|
|
||||||
logger.info("Saving local database and settings...");
|
logger.info("Saving local database and settings...");
|
||||||
localDB.save();
|
localDb.save();
|
||||||
Settings.getInstance().save();
|
Settings.getInstance().save();
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
logger.log(Level.SEVERE, "Unable to save local files", e);
|
logger.log(Level.SEVERE, "Unable to save local files", e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
@ -66,6 +66,18 @@ public class ComponentList<E> extends JPanel {
|
|||||||
synchronizeModel();
|
synchronizeModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all child components and then adds all components representing the
|
||||||
|
* elements of the {@link ComponentListModel}.
|
||||||
|
*
|
||||||
|
* @since Envoy v0.3-alpha
|
||||||
|
*/
|
||||||
|
public void synchronizeModel() {
|
||||||
|
removeAll();
|
||||||
|
if (model != null) for (E elem : model)
|
||||||
|
add(elem);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an object to the list by rendering it with the current
|
* Adds an object to the list by rendering it with the current
|
||||||
* {@link ComponentListCellRenderer}.
|
* {@link ComponentListCellRenderer}.
|
||||||
@ -76,16 +88,4 @@ public class ComponentList<E> extends JPanel {
|
|||||||
void add(E elem) {
|
void add(E elem) {
|
||||||
add(renderer.getListCellComponent(this, elem, false));
|
add(renderer.getListCellComponent(this, elem, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all child components and then adds all components representing the
|
|
||||||
* elements of the {@link ComponentListModel}.
|
|
||||||
*
|
|
||||||
* @since Envoy v0.3-alpha
|
|
||||||
*/
|
|
||||||
void synchronizeModel() {
|
|
||||||
removeAll();
|
|
||||||
if (model != null) for (E elem : model)
|
|
||||||
add(elem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -78,6 +78,12 @@ public final class ComponentListModel<E> implements Iterable<E>, Serializable {
|
|||||||
*/
|
*/
|
||||||
public int size() { return elements.size(); }
|
public int size() { return elements.size(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if this model contains no elements
|
||||||
|
* @see java.util.List#isEmpty()
|
||||||
|
*/
|
||||||
|
public boolean isEmpty() { return elements.isEmpty(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return an iterator over the elements of this list model
|
* @return an iterator over the elements of this list model
|
||||||
* @see java.util.List#iterator()
|
* @see java.util.List#iterator()
|
||||||
|
Reference in New Issue
Block a user