diff --git a/client/src/main/java/envoy/client/data/ClientConfig.java b/client/src/main/java/envoy/client/data/ClientConfig.java index 49e7892..3a18f08 100644 --- a/client/src/main/java/envoy/client/data/ClientConfig.java +++ b/client/src/main/java/envoy/client/data/ClientConfig.java @@ -5,10 +5,8 @@ import static java.util.function.Function.identity; import java.io.File; import java.util.logging.Level; -import envoy.client.ui.Startup; import envoy.data.Config; import envoy.data.ConfigItem; -import envoy.data.LoginCredentials; /** * Implements a configuration specific to the Envoy Client with default values @@ -40,8 +38,8 @@ public class ClientConfig extends Config { items.put("localDB", new ConfigItem<>("localDB", "db", File::new, new File("localDB"), true)); items.put("ignoreLocalDB", new ConfigItem<>("ignoreLocalDB", "nodb", Boolean::parseBoolean, false, false)); items.put("homeDirectory", new ConfigItem<>("homeDirectory", "h", File::new, new File(System.getProperty("user.home"), ".envoy"), true)); - items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", Level::parse, Level.CONFIG, true)); - items.put("consoleLevelBarrier", new ConfigItem<>("consoleLevelBarrier", "cb", Level::parse, Level.FINEST, true)); + items.put("fileLevelBarrier", new ConfigItem<>("fileLevelBarrier", "fb", Level::parse, Level.OFF, true)); + items.put("consoleLevelBarrier", new ConfigItem<>("consoleLevelBarrier", "cb", Level::parse, Level.OFF, true)); items.put("user", new ConfigItem<>("user", "u", identity())); items.put("password", new ConfigItem<>("password", "pw", identity())); } @@ -105,11 +103,4 @@ public class ClientConfig extends Config { * @since Envoy Client v0.3-alpha */ public boolean hasLoginCredentials() { return getUser() != null && getPassword() != null; } - - /** - * @return login credentials for the specified user name and password, without - * the registration option - * @since Envoy Client v0.3-alpha - */ - public LoginCredentials getLoginCredentials() { return new LoginCredentials(getUser(), getPassword(), false, Startup.VERSION); } } diff --git a/client/src/main/java/envoy/client/data/GroupChat.java b/client/src/main/java/envoy/client/data/GroupChat.java index aafd3a0..6592e8e 100644 --- a/client/src/main/java/envoy/client/data/GroupChat.java +++ b/client/src/main/java/envoy/client/data/GroupChat.java @@ -1,7 +1,7 @@ package envoy.client.data; import java.io.IOException; -import java.time.LocalDateTime; +import java.time.Instant; import envoy.client.net.WriteProxy; import envoy.data.Contact; @@ -46,7 +46,7 @@ public class GroupChat extends Chat { else { gmsg.getMemberStatuses().replace(sender.getID(), MessageStatus.READ); writeProxy - .writeMessageStatusChange(new GroupMessageStatusChange(gmsg.getID(), MessageStatus.READ, LocalDateTime.now(), sender.getID())); + .writeMessageStatusChange(new GroupMessageStatusChange(gmsg.getID(), MessageStatus.READ, Instant.now(), sender.getID())); } } } diff --git a/client/src/main/java/envoy/client/data/LocalDB.java b/client/src/main/java/envoy/client/data/LocalDB.java index 421cb38..d344d98 100644 --- a/client/src/main/java/envoy/client/data/LocalDB.java +++ b/client/src/main/java/envoy/client/data/LocalDB.java @@ -1,5 +1,6 @@ package envoy.client.data; +import java.time.Instant; import java.util.*; import envoy.data.*; @@ -20,11 +21,12 @@ import envoy.event.NameChange; */ public abstract class LocalDB { - protected User user; - protected Map users = new HashMap<>(); - protected List chats = new ArrayList<>(); - protected IDGenerator idGenerator; - protected CacheMap cacheMap = new CacheMap(); + protected User user; + protected Map users = new HashMap<>(); + protected List chats = new ArrayList<>(); + protected IDGenerator idGenerator; + protected CacheMap cacheMap = new CacheMap(); + protected Instant lastSync = Instant.EPOCH; { cacheMap.put(Message.class, new Cache<>()); @@ -42,10 +44,11 @@ public abstract class LocalDB { * 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. * + * @param isOnline determines which {@code lastSync} time stamp is saved * @throws Exception if the saving process failed * @since Envoy Client v0.3-alpha */ - public void save() throws Exception {} + public void save(boolean isOnline) throws Exception {} /** * Loads all user data. @@ -77,7 +80,7 @@ public abstract class LocalDB { * @since Envoy Client v0.1-beta */ public void synchronize() { - user.getContacts().stream().filter(u -> u instanceof User && !users.containsKey(u.getName())).forEach(u -> users.put(u.getName(), u)); + user.getContacts().stream().filter(u -> u instanceof User && !users.containsKey(u.getName())).forEach(u -> users.put(u.getName(), (User) u)); users.put(user.getName(), user); // Synchronize user status data @@ -98,7 +101,7 @@ public abstract class LocalDB { * user names as keys * @since Envoy Client v0.2-alpha */ - public Map getUsers() { return users; } + public Map getUsers() { return users; } /** * @return all saved {@link Chat} objects that list the client user as the @@ -148,6 +151,12 @@ public abstract class LocalDB { */ public CacheMap getCacheMap() { return cacheMap; } + /** + * @return the time stamp when the database was last saved + * @since Envoy Client v0.2-beta + */ + public Instant getLastSync() { return lastSync; } + /** * Searches for a message by ID. * diff --git a/client/src/main/java/envoy/client/data/PersistentLocalDB.java b/client/src/main/java/envoy/client/data/PersistentLocalDB.java index 182a4c8..1b0c310 100644 --- a/client/src/main/java/envoy/client/data/PersistentLocalDB.java +++ b/client/src/main/java/envoy/client/data/PersistentLocalDB.java @@ -1,6 +1,7 @@ package envoy.client.data; import java.io.*; +import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; @@ -26,7 +27,7 @@ public final class PersistentLocalDB extends LocalDB { /** * Constructs an empty local database. To serialize any user-specific data to * the file system, call {@link PersistentLocalDB#initializeUserStorage()} first - * and then {@link PersistentLocalDB#save()}. + * and then {@link PersistentLocalDB#save(boolean)}. * * @param dbDir the directory in which to persist data * @throws IOException if {@code dbDir} is a file (and not a directory) @@ -57,12 +58,12 @@ public final class PersistentLocalDB extends LocalDB { } @Override - public void save() throws IOException { + public void save(boolean isOnline) throws IOException { // Save users SerializationUtils.write(usersFile, users); - // Save user data - if (user != null) SerializationUtils.write(userFile, chats, cacheMap); + // Save user data and last sync time stamp + if (user != null) SerializationUtils.write(userFile, chats, cacheMap, isOnline ? Instant.now() : lastSync); // Save id generator if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator); @@ -76,6 +77,7 @@ public final class PersistentLocalDB extends LocalDB { try (var in = new ObjectInputStream(new FileInputStream(userFile))) { chats = (ArrayList) in.readObject(); cacheMap = (CacheMap) in.readObject(); + lastSync = (Instant) in.readObject(); } } diff --git a/client/src/main/java/envoy/client/net/ReceivedMessageProcessor.java b/client/src/main/java/envoy/client/net/ReceivedMessageProcessor.java index f83815c..84aa7dc 100644 --- a/client/src/main/java/envoy/client/net/ReceivedMessageProcessor.java +++ b/client/src/main/java/envoy/client/net/ReceivedMessageProcessor.java @@ -1,14 +1,11 @@ package envoy.client.net; import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.logging.Logger; import envoy.client.event.MessageCreationEvent; import envoy.data.Message; import envoy.data.Message.MessageStatus; import envoy.event.EventBus; -import envoy.util.EnvoyLog; /** * Project: envoy-client
@@ -20,17 +17,12 @@ import envoy.util.EnvoyLog; */ public class ReceivedMessageProcessor implements Consumer { - private static final Logger logger = EnvoyLog.getLogger(ReceivedMessageProcessor.class); - @Override public void accept(Message message) { - if (message.getStatus() != MessageStatus.SENT) logger.log(Level.WARNING, "The message has the unexpected status " + message.getStatus()); - else { - // Update status to RECEIVED - message.nextStatus(); + // Update status to RECEIVED + if (message.getStatus() == MessageStatus.SENT) message.nextStatus(); - // Dispatch event - EventBus.getInstance().dispatch(new MessageCreationEvent(message)); - } + // Dispatch event + EventBus.getInstance().dispatch(new MessageCreationEvent(message)); } } diff --git a/client/src/main/java/envoy/client/ui/Startup.java b/client/src/main/java/envoy/client/ui/Startup.java index 99f163e..464b8e3 100644 --- a/client/src/main/java/envoy/client/ui/Startup.java +++ b/client/src/main/java/envoy/client/ui/Startup.java @@ -121,12 +121,13 @@ public final class Startup extends Application { @Override public void stop() { try { + logger.log(Level.INFO, "Saving local database and settings..."); + localDB.save(client.isOnline()); + Settings.getInstance().save(); + logger.log(Level.INFO, "Closing connection..."); client.close(); - logger.log(Level.INFO, "Saving local database and settings..."); - localDB.save(); - Settings.getInstance().save(); logger.log(Level.INFO, "Envoy was terminated by its user"); } catch (final Exception e) { logger.log(Level.SEVERE, "Unable to save local files: ", e); diff --git a/client/src/main/java/envoy/client/ui/controller/ChatScene.java b/client/src/main/java/envoy/client/ui/controller/ChatScene.java index 83e64fe..f32ea08 100644 --- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -1,5 +1,7 @@ package envoy.client.ui.controller; +import static envoy.data.Message.MessageStatus.RECEIVED; + import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.io.ByteArrayInputStream; @@ -134,7 +136,13 @@ public final class ChatScene implements Restorable { // Listen to received messages eventBus.register(MessageCreationEvent.class, e -> { final var message = e.get(); - localDB.getChat(message instanceof GroupMessage ? message.getRecipientID() : message.getSenderID()).ifPresent(chat -> { + + // The sender of the message is the recipient of the chat + // Exceptions: this user is the sender (sync) or group message (group is + // recipient) + final long recipientID = message instanceof GroupMessage || message.getSenderID() == localDB.getUser().getID() ? message.getRecipientID() + : message.getSenderID(); + localDB.getChat(recipientID).ifPresent(chat -> { chat.insert(message); if (chat.equals(currentChat)) { try { @@ -143,8 +151,10 @@ public final class ChatScene implements Restorable { logger.log(Level.WARNING, "Could not read current chat: ", e1); } Platform.runLater(() -> { ListViewRefresh.deepRefresh(messageList); scrollToMessageListEnd(); }); - } else chat.incrementUnreadAmount(); - // Moving chat with most recent unreadMessages to the top + // TODO: Increment unread counter for group messages with status < RECEIVED + } else if (message.getSenderID() != localDB.getUser().getID() && message.getStatus() == RECEIVED) chat.incrementUnreadAmount(); + + // Move chat with most recent unread messages to the top Platform.runLater(() -> { chatList.getItems().remove(chat); chatList.getItems().add(0, chat); @@ -182,12 +192,11 @@ public final class ChatScene implements Restorable { final var contact = e.get(); switch (e.getOperationType()) { case ADD: - localDB.getUsers().put(contact.getName(), contact); - final Chat chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact); + if (contact instanceof User) localDB.getUsers().put(contact.getName(), (User) contact); + Chat chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact); Platform.runLater(() -> chatList.getItems().add(chat)); break; case REMOVE: - localDB.getUsers().remove(contact.getName()); Platform.runLater(() -> chatList.getItems().removeIf(c -> c.getRecipient().equals(contact))); break; } diff --git a/client/src/main/java/envoy/client/ui/controller/LoginScene.java b/client/src/main/java/envoy/client/ui/controller/LoginScene.java index 3f0df5b..93978e8 100644 --- a/client/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/client/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -2,6 +2,7 @@ package envoy.client.ui.controller; import java.io.FileNotFoundException; import java.io.IOException; +import java.time.Instant; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; @@ -94,7 +95,8 @@ public final class LoginScene { userTextField.requestFocus(); // Perform automatic login if configured - if (config.hasLoginCredentials()) performHandshake(config.getLoginCredentials()); + if (config.hasLoginCredentials()) + performHandshake(new LoginCredentials(config.getUser(), config.getPassword(), false, Startup.VERSION, loadLastSync(config.getUser()))); } @FXML @@ -108,12 +110,13 @@ public final class LoginScene { new Alert(AlertType.ERROR, "The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait(); userTextField.getTextField().clear(); } else performHandshake(new LoginCredentials(userTextField.getTextField().getText(), passwordField.getText(), registerCheckBox.isSelected(), - Startup.VERSION)); + Startup.VERSION, loadLastSync(userTextField.getTextField().getText()))); } @FXML private void offlineModeButtonPressed() { - attemptOfflineMode(new LoginCredentials(userTextField.getTextField().getText(), passwordField.getText(), false, Startup.VERSION)); + attemptOfflineMode( + new LoginCredentials(userTextField.getTextField().getText(), passwordField.getText(), false, Startup.VERSION, localDB.getLastSync())); } @FXML @@ -130,6 +133,18 @@ public final class LoginScene { System.exit(0); } + private Instant loadLastSync(String identifier) { + try { + localDB.loadUsers(); + localDB.setUser(localDB.getUsers().get(identifier)); + localDB.initializeUserStorage(); + localDB.loadUserData(); + } catch (Exception e) { + // User storage empty, wrong user name etc. -> default lastSync + } + return localDB.getLastSync(); + } + private void performHandshake(LoginCredentials credentials) { try { client.performHandshake(credentials, cacheMap); diff --git a/client/src/main/java/envoy/client/ui/listcell/MessageControl.java b/client/src/main/java/envoy/client/ui/listcell/MessageControl.java index 8b7115b..a6ecae5 100644 --- a/client/src/main/java/envoy/client/ui/listcell/MessageControl.java +++ b/client/src/main/java/envoy/client/ui/listcell/MessageControl.java @@ -3,6 +3,7 @@ package envoy.client.ui.listcell; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.io.ByteArrayInputStream; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Map; import java.util.logging.Level; @@ -35,11 +36,12 @@ import envoy.util.EnvoyLog; */ public class MessageControl extends Label { - private static User client; - private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); - private static final Map statusImages = IconUtil.loadByEnum(MessageStatus.class, 16); + private static User client; - private static final Logger logger = EnvoyLog.getLogger(MessageControl.class); + private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss") + .withZone(ZoneId.systemDefault()); + private static final Map statusImages = IconUtil.loadByEnum(MessageStatus.class, 16); + private static final Logger logger = EnvoyLog.getLogger(MessageControl.class); /** * @@ -68,7 +70,8 @@ public class MessageControl extends Label { if (message.hasAttachment()) { switch (message.getAttachment().getType()) { case PICTURE: - vbox.getChildren().add(new ImageView(new Image(new ByteArrayInputStream(message.getAttachment().getData()), 256, 256, true, true))); + vbox.getChildren() + .add(new ImageView(new Image(new ByteArrayInputStream(message.getAttachment().getData()), 256, 256, true, true))); break; case VIDEO: break; diff --git a/client/src/main/resources/client.properties b/client/src/main/resources/client.properties index 609d61a..25f1ffd 100644 --- a/client/src/main/resources/client.properties +++ b/client/src/main/resources/client.properties @@ -1,3 +1,4 @@ server=localhost port=8080 -localDB=.\\localDB +localDB=localDB +consoleLevelBarrier=FINER diff --git a/common/src/main/java/envoy/data/Contact.java b/common/src/main/java/envoy/data/Contact.java index 9a5fd0a..3fe71da 100644 --- a/common/src/main/java/envoy/data/Contact.java +++ b/common/src/main/java/envoy/data/Contact.java @@ -17,9 +17,10 @@ import java.util.Set; */ public abstract class Contact implements Serializable { - private final long id; - private final transient Set contacts; - private String name; + protected final long id; + protected final transient Set contacts; + + protected String name; private static final long serialVersionUID = 0L; @@ -55,19 +56,13 @@ public abstract class Contact implements Serializable { */ public void setName(String name) { this.name = name; } - /** - * {@inheritDoc} - */ - @Override - public String toString() { return String.format("Contact[id=%d,name=%s, contacts=%s]", id, name, contacts); } - /** * Provides a hash code based on the ID of this contact. * * @since Envoy Common v0.1-beta */ @Override - public int hashCode() { return Objects.hash(id); } + public final int hashCode() { return Objects.hash(id); } /** * Tests equality to another object. If that object is a contact as well, @@ -77,7 +72,7 @@ public abstract class Contact implements Serializable { * @return {code true} if both objects are contacts and have identical IDs */ @Override - public boolean equals(Object obj) { + public final boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Contact)) return false; return id == ((Contact) obj).id; diff --git a/common/src/main/java/envoy/data/Group.java b/common/src/main/java/envoy/data/Group.java index 4eb6b02..53f816f 100644 --- a/common/src/main/java/envoy/data/Group.java +++ b/common/src/main/java/envoy/data/Group.java @@ -36,6 +36,9 @@ public final class Group extends Contact { */ public Group(long id, String name, Set members) { super(id, name, members); } + @Override + public String toString() { return String.format("Group[id=%d,name=%s,%d member(s)]", id, name, contacts.size()); } + private void readObject(ObjectInputStream inputStream) throws Exception { inputStream.defaultReadObject(); var contacts = Contact.class.getDeclaredField("contacts"); @@ -51,5 +54,5 @@ public final class Group extends Contact { @SuppressWarnings("unchecked") @Override - public Set getContacts() { return (Set) super.getContacts(); } + public Set getContacts() { return (Set) contacts; } } diff --git a/common/src/main/java/envoy/data/GroupMessage.java b/common/src/main/java/envoy/data/GroupMessage.java index 7fb0d4a..93b8658 100644 --- a/common/src/main/java/envoy/data/GroupMessage.java +++ b/common/src/main/java/envoy/data/GroupMessage.java @@ -1,6 +1,6 @@ package envoy.data; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.Collections; import java.util.Map; @@ -16,7 +16,7 @@ public final class GroupMessage extends Message { private final Map memberStatuses; - private static final long serialVersionUID = 0L; + private static final long serialVersionUID = 1L; /** * Initializes a {@link GroupMessage} with values for all of its properties. The @@ -38,9 +38,9 @@ public final class GroupMessage extends Message { * @param forwarded whether this message was forwarded * @param memberStatuses a map of all members and their status according to this * {@link GroupMessage} - * @since Envoy Common v0.1-beta + * @since Envoy Common v0.2-beta */ - GroupMessage(long id, long senderID, long groupID, LocalDateTime creationDate, LocalDateTime receivedDate, LocalDateTime readDate, String text, + GroupMessage(long id, long senderID, long groupID, Instant creationDate, Instant receivedDate, Instant readDate, String text, Attachment attachment, MessageStatus status, boolean forwarded, Map memberStatuses) { super(id, senderID, groupID, creationDate, receivedDate, readDate, text, attachment, status, forwarded); this.memberStatuses = memberStatuses; @@ -55,10 +55,10 @@ public final class GroupMessage extends Message { setStatus(Collections.min(memberStatuses.values())); switch (getStatus()) { case RECEIVED: - setReceivedDate(LocalDateTime.now()); + setReceivedDate(Instant.now()); break; case READ: - setReadDate(LocalDateTime.now()); + setReadDate(Instant.now()); break; } } diff --git a/common/src/main/java/envoy/data/LoginCredentials.java b/common/src/main/java/envoy/data/LoginCredentials.java index 6257c10..0bef595 100644 --- a/common/src/main/java/envoy/data/LoginCredentials.java +++ b/common/src/main/java/envoy/data/LoginCredentials.java @@ -1,6 +1,7 @@ package envoy.data; import java.io.Serializable; +import java.time.Instant; /** * Contains a {@link User}'s login / registration information as well as the @@ -17,8 +18,9 @@ public final class LoginCredentials implements Serializable { private final String identifier, password, clientVersion; private final boolean registration; + private final Instant lastSync; - private static final long serialVersionUID = 2; + private static final long serialVersionUID = 3; /** * Initializes login credentials for a handshake. @@ -28,18 +30,24 @@ public final class LoginCredentials implements Serializable { * @param registration signifies that these credentials are used for user * registration instead of user login * @param clientVersion the version of the client sending these credentials - * @since Envoy Common v0.1-beta + * @param lastSync the time stamp of the last synchronization + * @since Envoy Common v0.2-beta */ - public LoginCredentials(String identifier, String password, boolean registration, String clientVersion) { + public LoginCredentials(String identifier, String password, boolean registration, String clientVersion, Instant lastSync) { this.identifier = identifier; this.password = password; this.registration = registration; this.clientVersion = clientVersion; + this.lastSync = lastSync; } @Override public String toString() { - return String.format("LoginCredentials[identifier=%s,registration=%b,clientVersion=%s]", identifier, registration, clientVersion); + return String.format("LoginCredentials[identifier=%s,registration=%b,clientVersion=%s,lastSync=%s]", + identifier, + registration, + clientVersion, + lastSync); } /** @@ -66,4 +74,10 @@ public final class LoginCredentials implements Serializable { * @since Envoy Common v0.1-beta */ public String getClientVersion() { return clientVersion; } + + /** + * @return the time stamp of the last synchronization + * @since Envoy Common v0.2-beta + */ + public Instant getLastSync() { return lastSync; } } diff --git a/common/src/main/java/envoy/data/Message.java b/common/src/main/java/envoy/data/Message.java index 040ad59..8d31bd3 100644 --- a/common/src/main/java/envoy/data/Message.java +++ b/common/src/main/java/envoy/data/Message.java @@ -1,8 +1,7 @@ package envoy.data; import java.io.Serializable; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; +import java.time.Instant; /** * Represents a unique message with a unique, numeric ID. Further metadata @@ -50,14 +49,14 @@ public class Message implements Serializable { private final long id, senderID, recipientID; private final boolean forwarded; - private final LocalDateTime creationDate; + private final Instant creationDate; private final String text; private final Attachment attachment; - private LocalDateTime receivedDate, readDate; + private Instant receivedDate, readDate; private MessageStatus status; - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; /** * Initializes a {@link Message} with values for all of its properties. The use @@ -75,9 +74,9 @@ public class Message implements Serializable { * @param attachment the attachment of the message, if present * @param status the current {@link MessageStatus} of the message * @param forwarded whether this message was forwarded - * @since Envoy Common v0.2-alpha + * @since Envoy Common v0.2-beta */ - Message(long id, long senderID, long recipientID, LocalDateTime creationDate, LocalDateTime receivedDate, LocalDateTime readDate, String text, + Message(long id, long senderID, long recipientID, Instant creationDate, Instant receivedDate, Instant readDate, String text, Attachment attachment, MessageStatus status, boolean forwarded) { this.id = id; this.senderID = senderID; @@ -115,7 +114,7 @@ public class Message implements Serializable { id, senderID, recipientID, - DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss").format(creationDate), + creationDate, status, text, forwarded, @@ -142,34 +141,34 @@ public class Message implements Serializable { /** * @return the date at which this message was created - * @since Envoy Common v0.2-alpha + * @since Envoy Common v0.2-beta */ - public LocalDateTime getCreationDate() { return creationDate; } + public Instant getCreationDate() { return creationDate; } /** * @return the date at which the message has been received by the sender - * @since Envoy Common v0.2-alpha + * @since Envoy Common v0.2-beta */ - public LocalDateTime getReceivedDate() { return receivedDate; } + public Instant getReceivedDate() { return receivedDate; } /** * @param receivedDate the date at which the message has been received by the * sender - * @since Envoy Common v0.2-alpha + * @since Envoy Common v0.2-beta */ - public void setReceivedDate(LocalDateTime receivedDate) { this.receivedDate = receivedDate; } + public void setReceivedDate(Instant receivedDate) { this.receivedDate = receivedDate; } /** * @return the date at which the message has been read by the sender - * @since Envoy Common v0.2-alpha + * @since Envoy Common v0.2-beta */ - public LocalDateTime getReadDate() { return readDate; } + public Instant getReadDate() { return readDate; } /** * @param readDate at which the message has been read by the sender - * @since Envoy Common v0.2-alpha + * @since Envoy Common v0.2-beta */ - public void setReadDate(LocalDateTime readDate) { this.readDate = readDate; } + public void setReadDate(Instant readDate) { this.readDate = readDate; } /** * @return the text content of this message diff --git a/common/src/main/java/envoy/data/MessageBuilder.java b/common/src/main/java/envoy/data/MessageBuilder.java index 917adc4..82df03d 100644 --- a/common/src/main/java/envoy/data/MessageBuilder.java +++ b/common/src/main/java/envoy/data/MessageBuilder.java @@ -1,6 +1,6 @@ package envoy.data; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.HashMap; import java.util.Map; @@ -23,7 +23,7 @@ public class MessageBuilder { // Properties with default values private long id; - private LocalDateTime creationDate, receivedDate, readDate; + private Instant creationDate, receivedDate, readDate; private String text; private Attachment attachment; private Message.MessageStatus status; @@ -70,7 +70,7 @@ public class MessageBuilder { public MessageBuilder(Message msg, long recipientID, IDGenerator iDGenerator) { this(msg.getRecipientID(), recipientID, iDGenerator.next()); this.attachment = msg.getAttachment(); - this.creationDate = LocalDateTime.now(); + this.creationDate = Instant.now(); this.forwarded = true; this.text = msg.getText(); this.status = MessageStatus.WAITING; @@ -83,7 +83,7 @@ public class MessageBuilder { * * * - * * * @@ -113,8 +113,8 @@ public class MessageBuilder { *
*
{@code date}{@code LocalDateTime.now()} and {@code null} for {@code receivedDate} and + * {@code Instant.now()} and {@code null} for {@code receivedDate} and * {@code readDate}
* - * - * + * + * * * * @@ -140,8 +140,8 @@ public class MessageBuilder { *
*
{@code date}{@code new Date()}{@code time stamp}{@code Instant.now()}
{@code text}
* - * - * + * + * * * * @@ -162,7 +162,7 @@ public class MessageBuilder { } private void supplyDefaults() { - if (creationDate == null) creationDate = LocalDateTime.now(); + if (creationDate == null) creationDate = Instant.now(); if (text == null) text = ""; if (status == null) status = MessageStatus.WAITING; } @@ -170,9 +170,9 @@ public class MessageBuilder { /** * @param creationDate the creation date of the {@link Message} to create * @return this {@link MessageBuilder} - * @since Envoy Common v0.2-alpha + * @since Envoy Common v0.2-beta */ - public MessageBuilder setCreationDate(LocalDateTime creationDate) { + public MessageBuilder setCreationDate(Instant creationDate) { this.creationDate = creationDate; return this; } @@ -180,9 +180,9 @@ public class MessageBuilder { /** * @param receivedDate the received date of the {@link Message} to create * @return this {@link MessageBuilder} - * @since Envoy Common v0.1-beta + * @since Envoy Common v0.2-beta */ - public MessageBuilder setReceivedDate(LocalDateTime receivedDate) { + public MessageBuilder setReceivedDate(Instant receivedDate) { this.receivedDate = receivedDate; return this; } @@ -190,9 +190,9 @@ public class MessageBuilder { /** * @param readDate the read date of the {@link Message} to create * @return this {@link MessageBuilder} - * @since Envoy Common v0.1-beta + * @since Envoy Common v0.2-beta */ - public MessageBuilder setReadDate(LocalDateTime readDate) { + public MessageBuilder setReadDate(Instant readDate) { this.readDate = readDate; return this; } diff --git a/common/src/main/java/envoy/data/User.java b/common/src/main/java/envoy/data/User.java index 7751ce3..a423934 100644 --- a/common/src/main/java/envoy/data/User.java +++ b/common/src/main/java/envoy/data/User.java @@ -99,7 +99,9 @@ public final class User extends Contact { } @Override - public String toString() { return String.format("User[id=%d,name=%s,status=%s,contacts=%s]", getID(), getName(), status, getContacts()); } + public String toString() { + return String.format("User[id=%d,name=%s,status=%s", id, name, status) + (contacts.isEmpty() ? "]" : "," + contacts.size() + " contact(s)]"); + } /** * @return the current status of this user diff --git a/common/src/main/java/envoy/event/GroupMessageStatusChange.java b/common/src/main/java/envoy/event/GroupMessageStatusChange.java index 01f63ae..6bf4626 100644 --- a/common/src/main/java/envoy/event/GroupMessageStatusChange.java +++ b/common/src/main/java/envoy/event/GroupMessageStatusChange.java @@ -1,6 +1,6 @@ package envoy.event; -import java.time.LocalDateTime; +import java.time.Instant; import envoy.data.GroupMessage; import envoy.data.Message.MessageStatus; @@ -27,9 +27,9 @@ public class GroupMessageStatusChange extends MessageStatusChange { * @param date the date at which the MessageStatus change occurred for * this specific member * @param memberID the ID of the group member that caused the status change - * @since Envoy Common v0.1-beta + * @since Envoy Common v0.2-beta */ - public GroupMessageStatusChange(long id, MessageStatus status, LocalDateTime date, long memberID) { + public GroupMessageStatusChange(long id, MessageStatus status, Instant date, long memberID) { super(id, status, date); this.memberID = memberID; } diff --git a/common/src/main/java/envoy/event/MessageStatusChange.java b/common/src/main/java/envoy/event/MessageStatusChange.java index 01e5c70..aa94e6d 100644 --- a/common/src/main/java/envoy/event/MessageStatusChange.java +++ b/common/src/main/java/envoy/event/MessageStatusChange.java @@ -1,6 +1,6 @@ package envoy.event; -import java.time.LocalDateTime; +import java.time.Instant; import envoy.data.Message; @@ -14,8 +14,8 @@ import envoy.data.Message; */ public class MessageStatusChange extends Event { - private final long id; - private final LocalDateTime date; + private final long id; + private final Instant date; private static final long serialVersionUID = 0L; @@ -26,9 +26,9 @@ public class MessageStatusChange extends Event { * @param status the status of the {@link Message} this event is related * to * @param date the date at which the MessageStatus change occurred - * @since Envoy Common v0.2-alpha + * @since Envoy Common v0.2-beta */ - public MessageStatusChange(long id, Message.MessageStatus status, LocalDateTime date) { + public MessageStatusChange(long id, Message.MessageStatus status, Instant date) { super(status); this.id = id; this.date = date; @@ -40,7 +40,7 @@ public class MessageStatusChange extends Event { * @param message the message from which to build the event * @since Envoy Common v0.2-alpha */ - public MessageStatusChange(Message message) { this(message.getID(), message.getStatus(), LocalDateTime.now()); } + public MessageStatusChange(Message message) { this(message.getID(), message.getStatus(), Instant.now()); } /** * @return the ID of the {@link Message} this event is related to @@ -50,9 +50,9 @@ public class MessageStatusChange extends Event { /** * @return the date at which the status change occurred - * @since Envoy Common v0.2-alpha + * @since Envoy Common v0.2-beta */ - public LocalDateTime getDate() { return date; } + public Instant getDate() { return date; } @Override public String toString() { return String.format("MessageStatusChange[id=%d,status=%s,date=%s]", id, value, date); } diff --git a/server/src/main/java/envoy/server/data/Contact.java b/server/src/main/java/envoy/server/data/Contact.java index c802837..43a9983 100644 --- a/server/src/main/java/envoy/server/data/Contact.java +++ b/server/src/main/java/envoy/server/data/Contact.java @@ -1,6 +1,6 @@ package envoy.server.data; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.Set; import javax.persistence.*; @@ -28,7 +28,7 @@ public abstract class Contact { protected String name; @Column(name = "creation_date") - private LocalDateTime creationDate; + private Instant creationDate; @ManyToMany(fetch = FetchType.EAGER) protected Set contacts; @@ -92,16 +92,16 @@ public abstract class Contact { /** * @return the creationDate - * @since Envoy Server Standalone v0.1-beta + * @since Envoy Server Standalone v0.2-beta */ - public LocalDateTime getCreationDate() { return creationDate; } + public Instant getCreationDate() { return creationDate; } /** * @param creationDate the creationDate to set - * @since Envoy Server Standalone v0.1-beta + * @since Envoy Server Standalone v0.2-beta */ - public void setCreationDate(LocalDateTime creationDate) { this.creationDate = creationDate; } + public void setCreationDate(Instant creationDate) { this.creationDate = creationDate; } @Override - public String toString() { return String.format("%s[id=%d,name=%s, %d contact(s)]", getClass().getSimpleName(), id, name, contacts.size()); } + public String toString() { return String.format("%s[id=%d,name=%s,%d contact(s)]", getClass().getSimpleName(), id, name, contacts.size()); } } diff --git a/server/src/main/java/envoy/server/data/GroupMessage.java b/server/src/main/java/envoy/server/data/GroupMessage.java index 90ad823..45be27d 100644 --- a/server/src/main/java/envoy/server/data/GroupMessage.java +++ b/server/src/main/java/envoy/server/data/GroupMessage.java @@ -1,6 +1,6 @@ package envoy.server.data; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.HashMap; import java.util.Map; @@ -19,10 +19,10 @@ import envoy.data.Group; @Entity @NamedQuery( name = GroupMessage.getPendingGroupMsg, - query = "SELECT m FROM GroupMessage m JOIN m.memberMessageStatus s WHERE (KEY(s) = :userId) AND ((m.creationDate > :lastSeen)" - + "OR ((m.status = envoy.data.Message$MessageStatus.RECEIVED) AND (m.receivedDate > :lastSeen))" - + "OR ((m.status = envoy.data.Message$MessageStatus.READ) AND (m.readDate > :lastSeen))" - + "OR ((m.lastStatusChangeDate > :lastSeen)))" + query = "SELECT m FROM GroupMessage m JOIN m.memberMessageStatus s WHERE KEY(s) = :userId AND (m.creationDate > :lastSeen " + + "OR m.status = envoy.data.Message$MessageStatus.RECEIVED AND m.receivedDate > :lastSeen " + + "OR m.status = envoy.data.Message$MessageStatus.READ AND m.readDate > :lastSeen " + + "OR m.lastStatusChangeDate > :lastSeen)" ) public class GroupMessage extends Message { @@ -39,7 +39,7 @@ public class GroupMessage extends Message { private Map memberMessageStatus; @Column(name = "last_status_change_date") - protected LocalDateTime lastStatusChangeDate; + protected Instant lastStatusChangeDate; /** * The constructor for a database object. @@ -55,9 +55,9 @@ public class GroupMessage extends Message { * into a * database {@link GroupMessage} * @param lastStatusChangeDate the time stamp to set - * @since Envoy Server Standalone v0.1-beta + * @since Envoy Server Standalone v0.2-beta */ - public GroupMessage(envoy.data.GroupMessage groupMessage, LocalDateTime lastStatusChangeDate) { + public GroupMessage(envoy.data.GroupMessage groupMessage, Instant lastStatusChangeDate) { super(groupMessage); memberMessageStatus = groupMessage.getMemberStatuses(); this.lastStatusChangeDate = lastStatusChangeDate; @@ -92,13 +92,13 @@ public class GroupMessage extends Message { /** * @return the date at which one of the member statuses changed last - * @since Envoy Server Standalone v0.1-beta + * @since Envoy Server Standalone v0.2-beta */ - public LocalDateTime getLastStatusChangeDate() { return lastStatusChangeDate; } + public Instant getLastStatusChangeDate() { return lastStatusChangeDate; } /** * @param date the date to set - * @since Envoy Server Standalone v0.1-beta + * @since Envoy Server Standalone v0.2-beta */ - public void setLastStatusChangeDate(LocalDateTime date) { lastStatusChangeDate = date; } + public void setLastStatusChangeDate(Instant date) { lastStatusChangeDate = date; } } diff --git a/server/src/main/java/envoy/server/data/Message.java b/server/src/main/java/envoy/server/data/Message.java index afe66c7..fc564a2 100755 --- a/server/src/main/java/envoy/server/data/Message.java +++ b/server/src/main/java/envoy/server/data/Message.java @@ -2,7 +2,7 @@ package envoy.server.data; import static envoy.data.Message.MessageStatus.*; -import java.time.LocalDateTime; +import java.time.Instant; import javax.persistence.*; @@ -12,11 +12,15 @@ import envoy.data.Message.MessageStatus; import envoy.data.MessageBuilder; /** - * This class serves as a way to let Hibernate communicate with the server - * without bringing the dependency of JPA/Hibernate into the client.
- * It will be referenced as "database message" to clarify between the different - * message objects.
- *
+ * This JPA entity, which will be referred to as database message, stores the + * information contained inside a {@link envoy.data.Message} inside the + * database, while having a slightly different data layout. + *

+ * A message can be converted to a database message by using the + * {@link Message#Message(envoy.data.Message)} constructor. A database message + * can be converted to a regular message using the {@link Message#toCommon()} + * method. In both cases, the objects will not contain references to each other. + *

* Project: envoy-server-standalone
* File: Message.java
* Created: 02.01.2020
@@ -29,9 +33,14 @@ import envoy.data.MessageBuilder; @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @NamedQuery( name = Message.getPending, - query = "SELECT m FROM Message m WHERE (m.recipient = :user AND m.status = envoy.data.Message$MessageStatus.SENT) " - + "OR (m.sender = :user) AND ((m.status = envoy.data.Message$MessageStatus.RECEIVED) AND (m.receivedDate > :lastSeen)" - + "OR (m.status = envoy.data.Message$MessageStatus.READ) AND (m.readDate > :lastSeen))" + query = "SELECT m FROM Message m WHERE " + // Send to or by the user before last seen + + "(m.sender = :user OR m.recipient = :user) AND m.creationDate > :lastSeen " + // SENT to the user + + "OR m.recipient = :user AND m.status = envoy.data.Message$MessageStatus.SENT " + // Sent by the user and RECEIVED / READ after last seen + + "OR m.sender = :user AND (m.status = envoy.data.Message$MessageStatus.RECEIVED AND m.receivedDate > :lastSeen " + + "OR m.status = envoy.data.Message$MessageStatus.READ AND m.readDate > :lastSeen)" ) public class Message { @@ -55,13 +64,13 @@ public class Message { protected Contact recipient; @Column(name = "creation_date") - protected LocalDateTime creationDate; + protected Instant creationDate; @Column(name = "received_date") - protected LocalDateTime receivedDate; + protected Instant receivedDate; @Column(name = "read_date") - protected LocalDateTime readDate; + protected Instant readDate; protected String text; protected envoy.data.Message.MessageStatus status; @@ -107,17 +116,14 @@ public class Message { * message * @since Envoy Server Standalone v0.1-alpha */ - public envoy.data.Message toCommon() { - return prepareBuilder().build(); - } + public envoy.data.Message toCommon() { return prepareBuilder().build(); } /** * @return a message builder containing the state of this message * @since Envoy Server Standalone v0.1-beta */ MessageBuilder prepareBuilder() { - var builder = new MessageBuilder(sender.getID(), recipient.getID(), id).setText( - text) + var builder = new MessageBuilder(sender.getID(), recipient.getID(), id).setText(text) .setCreationDate(creationDate) .setReceivedDate(receivedDate) .setReadDate(readDate) @@ -134,7 +140,7 @@ public class Message { * @since Envoy Server Standalone v0.1-beta */ public void received() { - receivedDate = LocalDateTime.now(); + receivedDate = Instant.now(); status = RECEIVED; } @@ -145,7 +151,7 @@ public class Message { * @since Envoy Server Standalone v0.1-beta */ public void read() { - readDate = LocalDateTime.now(); + readDate = Instant.now(); status = READ; } @@ -190,43 +196,43 @@ public class Message { /** * @return the date at which a {link envoy.data.Message} has been created - * @since Envoy Server Standalone v0.1-alpha + * @since Envoy Server Standalone v0.2-beta */ - public LocalDateTime getCreationDate() { return creationDate; } + public Instant getCreationDate() { return creationDate; } /** * @param creationDate the creation date to set - * @since Envoy Server Standalone v0.1-alpha + * @since Envoy Server Standalone v0.2-beta * @see Message#getCreationDate() */ - public void setCreationDate(LocalDateTime creationDate) { this.creationDate = creationDate; } + public void setCreationDate(Instant creationDate) { this.creationDate = creationDate; } /** * @return the date at which a {link envoy.data.Message} has been received by * the server - * @since Envoy Server Standalone v0.1-alpha + * @since Envoy Server Standalone v0.2-beta */ - public LocalDateTime getReceivedDate() { return receivedDate; } + public Instant getReceivedDate() { return receivedDate; } /** * @param receivedDate the received date to set - * @since Envoy Server Standalone v0.1-alpha + * @since Envoy Server Standalone v0.2-beta * @see Message#getReceivedDate() */ - public void setReceivedDate(LocalDateTime receivedDate) { this.receivedDate = receivedDate; } + public void setReceivedDate(Instant receivedDate) { this.receivedDate = receivedDate; } /** * @return the date at which a {link envoy.data.Message} has been read - * @since Envoy Server Standalone v0.1-alpha + * @since Envoy Server Standalone v0.2-beta */ - public LocalDateTime getReadDate() { return readDate; } + public Instant getReadDate() { return readDate; } /** * @param readDate the read date to set - * @since Envoy Server Standalone v0.1-alpha + * @since Envoy Server Standalone v0.2-beta * @see Message#getReadDate() */ - public void setReadDate(LocalDateTime readDate) { this.readDate = readDate; } + public void setReadDate(Instant readDate) { this.readDate = readDate; } /** * @return the status of a {link envoy.data.Message} diff --git a/server/src/main/java/envoy/server/data/PersistenceManager.java b/server/src/main/java/envoy/server/data/PersistenceManager.java index 3432846..7ec3524 100755 --- a/server/src/main/java/envoy/server/data/PersistenceManager.java +++ b/server/src/main/java/envoy/server/data/PersistenceManager.java @@ -1,6 +1,6 @@ package envoy.server.data; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.List; import javax.persistence.EntityManager; @@ -38,7 +38,7 @@ public class PersistenceManager { .getOnlineUsers() .stream() .map(this::getUserByID) - .forEach(user -> { user.setStatus(UserStatus.OFFLINE); user.setLastSeen(LocalDateTime.now()); entityManager.merge(user); }); + .forEach(user -> { user.setStatus(UserStatus.OFFLINE); user.setLastSeen(Instant.now()); entityManager.merge(user); }); transaction.commit(); })); } @@ -182,31 +182,29 @@ public class PersistenceManager { * Returns all messages received while being offline or the ones that have * changed. * - * @param user the user who wants to receive his unread messages + * @param user the user who wants to receive his unread messages + * @param lastSync the time stamp of the last synchronization * @return all messages that the client does not yet have (unread messages) - * @since Envoy Server Standalone v0.1-alpha + * @since Envoy Server Standalone v0.2-beta */ - public List getPendingMessages(User user) { - return entityManager - .createNamedQuery(Message.getPending) - .setParameter("user", user) - .setParameter("lastSeen", user.getLastSeen()) - .getResultList(); + public List getPendingMessages(User user, Instant lastSync) { + return entityManager.createNamedQuery(Message.getPending).setParameter("user", user).setParameter("lastSeen", lastSync).getResultList(); } /** * Returns all groupMessages received while being offline or the ones that have * changed. * - * @param user the user who wants to receive his unread groupMessages + * @param user the user who wants to receive his unread groupMessages + * @param lastSync the time stamp of the last synchronization * @return all groupMessages that the client does not yet have (unread * groupMessages) - * @since Envoy Server Standalone v0.1-alpha + * @since Envoy Server Standalone v0.2-beta */ - public List getPendingGroupMessages(User user) { + public List getPendingGroupMessages(User user, Instant lastSync) { return entityManager.createNamedQuery(GroupMessage.getPendingGroupMsg) .setParameter("userId", user.getID()) - .setParameter("lastSeen", user.getLastSeen()) + .setParameter("lastSeen", lastSync) .getResultList(); } @@ -221,8 +219,7 @@ public class PersistenceManager { * @since Envoy Server Standalone v0.1-alpha */ public List searchUsers(String searchPhrase, long userId) { - return entityManager.createNamedQuery( - User.searchByName) + return entityManager.createNamedQuery(User.searchByName) .setParameter("searchPhrase", searchPhrase + "%") .setParameter("context", getUserByID(userId)) .getResultList(); diff --git a/server/src/main/java/envoy/server/data/User.java b/server/src/main/java/envoy/server/data/User.java index 81b4d9e..b9e3d85 100755 --- a/server/src/main/java/envoy/server/data/User.java +++ b/server/src/main/java/envoy/server/data/User.java @@ -1,6 +1,6 @@ package envoy.server.data; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.Set; import java.util.stream.Collectors; @@ -66,7 +66,7 @@ public class User extends Contact { private String passwordHash; @Column(name = "last_seen") - private LocalDateTime lastSeen; + private Instant lastSeen; private UserStatus status; @@ -92,15 +92,15 @@ public class User extends Contact { /** * @return the last date the user has been online - * @since Envoy Server Standalone v0.1-alpha + * @since Envoy Server Standalone v0.2-beta */ - public LocalDateTime getLastSeen() { return lastSeen; } + public Instant getLastSeen() { return lastSeen; } /** * @param lastSeen the latest date at which the user has been online to set - * @since Envoy Server Standalone v0.1-alpha + * @since Envoy Server Standalone v0.2-beta */ - public void setLastSeen(LocalDateTime lastSeen) { this.lastSeen = lastSeen; } + public void setLastSeen(Instant lastSeen) { this.lastSeen = lastSeen; } /** * @return the status diff --git a/server/src/main/java/envoy/server/net/ConnectionManager.java b/server/src/main/java/envoy/server/net/ConnectionManager.java index e0ce7ed..99bc683 100755 --- a/server/src/main/java/envoy/server/net/ConnectionManager.java +++ b/server/src/main/java/envoy/server/net/ConnectionManager.java @@ -1,6 +1,6 @@ package envoy.server.net; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @@ -52,7 +52,7 @@ public class ConnectionManager implements ISocketIdListener { // Notify contacts of this users offline-going envoy.server.data.User user = PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID)); user.setStatus(UserStatus.OFFLINE); - user.setLastSeen(LocalDateTime.now()); + user.setLastSeen(Instant.now()); UserStatusChangeProcessor.updateUserStatus(user); // Remove the socket diff --git a/server/src/main/java/envoy/server/processors/GroupMessageProcessor.java b/server/src/main/java/envoy/server/processors/GroupMessageProcessor.java index 54594e3..8be1a57 100644 --- a/server/src/main/java/envoy/server/processors/GroupMessageProcessor.java +++ b/server/src/main/java/envoy/server/processors/GroupMessageProcessor.java @@ -2,7 +2,7 @@ package envoy.server.processors; import static envoy.data.Message.MessageStatus.*; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.Collections; import java.util.logging.Logger; @@ -56,7 +56,7 @@ public class GroupMessageProcessor implements ObjectProcessor { groupMessage); try { - PersistenceManager.getInstance().addMessage(new envoy.server.data.GroupMessage(groupMessage, LocalDateTime.now())); + PersistenceManager.getInstance().addMessage(new envoy.server.data.GroupMessage(groupMessage, Instant.now())); } catch (EntityExistsException e) { logger.warning("Received a groupMessage with an ID that already exists"); } diff --git a/server/src/main/java/envoy/server/processors/GroupMessageStatusChangeProcessor.java b/server/src/main/java/envoy/server/processors/GroupMessageStatusChangeProcessor.java index fa3bcb2..9e1077b 100644 --- a/server/src/main/java/envoy/server/processors/GroupMessageStatusChangeProcessor.java +++ b/server/src/main/java/envoy/server/processors/GroupMessageStatusChangeProcessor.java @@ -2,7 +2,7 @@ package envoy.server.processors; import static envoy.data.Message.MessageStatus.READ; -import java.time.LocalDateTime; +import java.time.Instant; import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; @@ -42,7 +42,7 @@ public class GroupMessageStatusChangeProcessor implements ObjectProcessor()); @@ -114,16 +114,17 @@ public final class LoginCredentialProcessor implements ObjectProcessor pendingGroupMessages = PersistenceManager.getInstance().getPendingGroupMessages(user); + List pendingGroupMessages = PersistenceManager.getInstance().getPendingGroupMessages(user, credentials.getLastSync()); logger.fine("Sending " + pendingGroupMessages.size() + " pending group messages to " + user + "..."); for (var gmsg : pendingGroupMessages) { final var gmsgCommon = gmsg.toCommon(); // Deliver the message to the user if he hasn't received it yet - if (gmsg.getMemberMessageStatus().get(user.getID()) == SENT) { - gmsg.getMemberMessageStatus().replace(user.getID(), RECEIVED); + if (gmsg.getCreationDate().isAfter(credentials.getLastSync()) || gmsg.getMemberMessageStatus().get(user.getID()) == SENT) { + if (gmsg.getMemberMessageStatus().replace(user.getID(), RECEIVED) != RECEIVED) { - gmsg.setLastStatusChangeDate(LocalDateTime.now()); + gmsg.setLastStatusChangeDate(Instant.now()); - writeProxy.write(socketID, gmsgCommon); + writeProxy.write(socketID, gmsgCommon); - // Notify all online group members about the status change - writeProxy.writeToOnlineContacts(gmsg.getRecipient().getContacts(), - new GroupMessageStatusChange(gmsg.getID(), RECEIVED, LocalDateTime - .now(), - connectionManager.getUserIDBySocketID(socketID))); - - if (Collections.min(gmsg.getMemberMessageStatus().values()) == RECEIVED) { - gmsg.received(); - - // Notify online members about the status change + // Notify all online group members about the status change writeProxy.writeToOnlineContacts(gmsg.getRecipient().getContacts(), - new MessageStatusChange(gmsg.getID(), gmsg.getStatus(), LocalDateTime.now())); - } + new GroupMessageStatusChange(gmsg.getID(), RECEIVED, Instant.now(), connectionManager.getUserIDBySocketID(socketID))); - PersistenceManager.getInstance().updateMessage(gmsg); + if (Collections.min(gmsg.getMemberMessageStatus().values()) == RECEIVED) { + gmsg.received(); + + // Notify online members about the status change + writeProxy.writeToOnlineContacts(gmsg.getRecipient().getContacts(), + new MessageStatusChange(gmsg.getID(), gmsg.getStatus(), Instant.now())); + } + + PersistenceManager.getInstance().updateMessage(gmsg); + } else { + + // Just send the message without updating if it was received in the past + writeProxy.write(socketID, gmsgCommon); + } } else { // Sending group message status changes if (gmsg.getStatus() == SENT && gmsg.getLastStatusChangeDate().isAfter(gmsg.getCreationDate()) || gmsg.getStatus() == RECEIVED && gmsg.getLastStatusChangeDate().isAfter(gmsg.getReceivedDate())) { - gmsg.getMemberMessageStatus().forEach((memberID, memberStatus) -> - writeProxy.write(socketID, new GroupMessageStatusChange(gmsg.getID(), memberStatus, gmsg.getLastStatusChangeDate(), memberID))); + gmsg.getMemberMessageStatus() + .forEach((memberID, memberStatus) -> writeProxy.write(socketID, + new GroupMessageStatusChange(gmsg.getID(), memberStatus, gmsg.getLastStatusChangeDate(), memberID))); } // Deliver just a status change instead of the whole message @@ -182,6 +187,9 @@ public final class LoginCredentialProcessor implements ObjectProcessor

{@code date}{@code new Date()}{@code time stamp}{@code Instant.now()}
{@code text}