From fff9fb90fabc7c6ac461db2b47323a3d50477e2f Mon Sep 17 00:00:00 2001 From: bloodwiing Date: Wed, 13 Dec 2023 11:25:41 +0200 Subject: [PATCH] Late-pulling --- .../dev/wiing/gossip/client/Connection.java | 116 +++++++++----- .../dev/wiing/gossip/client/DataCache.java | 48 ++++++ .../dev/wiing/gossip/client/GossipApp.java | 2 +- .../controllers/MainChatController.java | 31 +++- .../client/controllers/MainController.java | 151 ++++++++++++++---- .../item/MessageItemController.java | 18 +-- .../item/SystemMessageItemController.java | 76 +++++++++ .../wiing/gossip/client/views/chat-view.fxml | 60 +------ .../client/views/system-message-item.fxml | 33 ++++ .../dev/wiing/gossip/lib/PacketHandler.java | 26 ++- .../dev/wiing/gossip/lib/PacketManager.java | 24 ++- .../dev/wiing/gossip/lib/models/Message.java | 24 ++- .../gossip/lib/models/SystemMessage.java | 43 +++++ .../dev/wiing/gossip/lib/models/Topic.java | 12 ++ .../wiing/gossip/lib/models/UserMessage.java | 26 +++ .../lib/packets/MessageCreatedPacket.java | 15 +- .../gossip/lib/packets/MessageDataPacket.java | 151 ++++++++++++++++++ .../lib/packets/MessageFetchPacket.java | 48 ++++++ .../lib/packets/MessageListDataPacket.java | 59 +++++++ .../lib/packets/MessageListFetchPacket.java | 37 +++++ .../lib/packets/MessageSystemPacket.java | 86 ++++++++++ .../gossip/lib/packets/TopicDataPacket.java | 109 +++++++++++++ .../gossip/lib/packets/TopicFetchPacket.java | 37 +++++ .../lib/packets/TopicListDataPacket.java | 48 ++++++ .../lib/packets/TopicListFetchPacket.java | 26 +++ .../dev/wiing/gossip/server/Database.java | 4 + .../dev/wiing/gossip/server/UserSocket.java | 143 +++++++++++++++-- 27 files changed, 1260 insertions(+), 193 deletions(-) create mode 100644 Client/src/main/java/dev/wiing/gossip/client/DataCache.java create mode 100644 Client/src/main/java/dev/wiing/gossip/client/controllers/item/SystemMessageItemController.java create mode 100644 Client/src/main/resources/dev/wiing/gossip/client/views/system-message-item.fxml create mode 100644 Lib/src/main/java/dev/wiing/gossip/lib/models/SystemMessage.java create mode 100644 Lib/src/main/java/dev/wiing/gossip/lib/models/UserMessage.java create mode 100644 Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageDataPacket.java create mode 100644 Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageFetchPacket.java create mode 100644 Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageListDataPacket.java create mode 100644 Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageListFetchPacket.java create mode 100644 Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageSystemPacket.java create mode 100644 Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicDataPacket.java create mode 100644 Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicFetchPacket.java create mode 100644 Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicListDataPacket.java create mode 100644 Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicListFetchPacket.java diff --git a/Client/src/main/java/dev/wiing/gossip/client/Connection.java b/Client/src/main/java/dev/wiing/gossip/client/Connection.java index 3fb5d4b..c316ac7 100644 --- a/Client/src/main/java/dev/wiing/gossip/client/Connection.java +++ b/Client/src/main/java/dev/wiing/gossip/client/Connection.java @@ -2,7 +2,9 @@ package dev.wiing.gossip.client; import dev.wiing.gossip.lib.PacketHandler; import dev.wiing.gossip.lib.PacketManager; +import dev.wiing.gossip.lib.data.LongData; import dev.wiing.gossip.lib.models.SecretUser; +import dev.wiing.gossip.lib.models.Topic; import dev.wiing.gossip.lib.models.User; import dev.wiing.gossip.lib.packets.*; @@ -103,8 +105,10 @@ public class Connection { } public Packet nextPacket(boolean useQueue) throws SocketException { - if (useQueue && !queuedPackets.isEmpty()) { - return queuedPackets.remove(0); + synchronized (queuedPackets) { + if (useQueue && !queuedPackets.isEmpty()) { + return queuedPackets.remove(0); + } } try { @@ -137,22 +141,24 @@ public class Connection { public Packet findPacketOfTypes(List types) { Packet packet = null; - int i; - for (i = 0; i < queuedPackets.size(); i++) { - Packet queuedPacket = queuedPackets.get(i); + synchronized (queuedPackets) { + int i; + for (i = 0; i < queuedPackets.size(); i++) { + Packet queuedPacket = queuedPackets.get(i); - if (types.contains(queuedPacket.getType())) { - packet = queuedPacket; - break; + if (types.contains(queuedPacket.getType())) { + packet = queuedPacket; + break; + } } - } - if (packet != null) { - queuedPackets.remove(i); - return packet; - } + if (packet != null) { + queuedPackets.remove(i); + return packet; + } - return null; + return null; + } } public boolean findAck(short acknowledgement) throws IOException { @@ -162,24 +168,26 @@ public class Connection { AckPacket ack = null; - int i; - for (i = 0; i < queuedPackets.size(); i++) { - Packet queuedPacket = queuedPackets.get(i); + synchronized (queuedPackets) { + int i; + for (i = 0; i < queuedPackets.size(); i++) { + Packet queuedPacket = queuedPackets.get(i); - if (queuedPacket.getType() == AckPacket.TYPE) { - ack = (AckPacket) queuedPacket; + if (queuedPacket.getType() == AckPacket.TYPE) { + ack = (AckPacket) queuedPacket; - if (ack.getAcknowledgement() == acknowledgement) { - break; + if (ack.getAcknowledgement() == acknowledgement) { + break; + } + + ack = null; } - - ack = null; } - } - if (ack != null) { - queuedPackets.remove(i); - return true; + if (ack != null) { + queuedPackets.remove(i); + return true; + } } } } @@ -198,17 +206,9 @@ public class Connection { )); } - private final Map userCache = new ConcurrentHashMap<>(); - - public User getUser(long userID) { - User result; - - if ((result = userCache.getOrDefault(userID, null)) != null) { - return result; - } - + private final DataCache userCache = new DataCache<>(id -> { UserFetchPacket fetch = new UserFetchPacket(); - fetch.setUserID(userID); + fetch.setUserID(id); Connection.getInstance().sendPacket(fetch); Packet resp; @@ -219,14 +219,50 @@ public class Connection { } if (resp.getType() == UserDataPacket.TYPE) { UserDataPacket userData = (UserDataPacket)resp; - result = new User( + return new User( userData.getUsername(), userData.getAvatarID(), userData.getUserID() ); - userCache.put(result.getUserID(), result); } - return result; + return null; + }); + + public User getUser(long userID) { + return userCache.get(userID); + } + + private final DataCache topicCache = new DataCache<>(id -> { + TopicFetchPacket fetch = new TopicFetchPacket(); + fetch.setTopicID(id); + Connection.getInstance().sendPacket(fetch); + + Packet resp; + try { + resp = Connection.getInstance().nextPacket(TopicDataPacket.TYPE); + } catch (SocketException e) { + throw new RuntimeException(e); + } + if (resp.getType() == TopicDataPacket.TYPE) { + TopicDataPacket topicData = (TopicDataPacket)resp; + Topic result = new Topic( + topicData.getTopicID(), + topicData.getTopicName(), + topicData.getTopicDescription(), + getUser(topicData.getHostID()), + topicData.getTopicColor() + ); + for (LongData userID : topicData.getUserIDs()) { + result.addUser(getUser(userID.getValue())); + } + return result; + } + + return null; + }); + + public Topic getTopic(long topicID) { + return topicCache.get(topicID); } } diff --git a/Client/src/main/java/dev/wiing/gossip/client/DataCache.java b/Client/src/main/java/dev/wiing/gossip/client/DataCache.java new file mode 100644 index 0000000..55fe150 --- /dev/null +++ b/Client/src/main/java/dev/wiing/gossip/client/DataCache.java @@ -0,0 +1,48 @@ +package dev.wiing.gossip.client; + +import dev.wiing.gossip.lib.data.LongData; +import dev.wiing.gossip.lib.models.Topic; +import dev.wiing.gossip.lib.packets.Packet; +import dev.wiing.gossip.lib.packets.TopicDataPacket; +import dev.wiing.gossip.lib.packets.TopicFetchPacket; + +import java.net.SocketException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class DataCache { + + private final DataFetchFunction fetchFunction; + + public DataCache(DataFetchFunction fetchFunction) { + this.fetchFunction = fetchFunction; + } + + @FunctionalInterface + public interface DataFetchFunction { + U fetch(long id); + } + + private final Map cache = new ConcurrentHashMap<>(); + + public T get(long id) { + if (id == 0) return null; + + T result; + + if ((result = cache.getOrDefault(id, null)) != null) { + return result; + } + + result = fetchFunction.fetch(id); + if (result != null) { + cache.put(id, result); + } + + return result; + } + + public void put(long id, T val) { + cache.put(id, val); + } +} diff --git a/Client/src/main/java/dev/wiing/gossip/client/GossipApp.java b/Client/src/main/java/dev/wiing/gossip/client/GossipApp.java index bb3817e..974166a 100644 --- a/Client/src/main/java/dev/wiing/gossip/client/GossipApp.java +++ b/Client/src/main/java/dev/wiing/gossip/client/GossipApp.java @@ -13,7 +13,7 @@ public class GossipApp extends Application { System.setProperty("prism.lcdtext", "false"); FXMLLoader fxmlLoader = new FXMLLoader(Program.class.getResource("views/login-view.fxml")); - Scene scene = new Scene(fxmlLoader.load(), 320, 240); + Scene scene = new Scene(fxmlLoader.load(), 500, 400); stage.setTitle("GossipApp"); stage.setScene(scene); diff --git a/Client/src/main/java/dev/wiing/gossip/client/controllers/MainChatController.java b/Client/src/main/java/dev/wiing/gossip/client/controllers/MainChatController.java index aa18cdb..f62e5e7 100644 --- a/Client/src/main/java/dev/wiing/gossip/client/controllers/MainChatController.java +++ b/Client/src/main/java/dev/wiing/gossip/client/controllers/MainChatController.java @@ -2,9 +2,11 @@ package dev.wiing.gossip.client.controllers; import dev.wiing.gossip.client.Connection; import dev.wiing.gossip.client.controllers.item.MessageItemController; +import dev.wiing.gossip.client.controllers.item.SystemMessageItemController; import dev.wiing.gossip.client.generic.Pair; import dev.wiing.gossip.client.utils.Utils; -import dev.wiing.gossip.lib.models.Message; +import dev.wiing.gossip.lib.models.SystemMessage; +import dev.wiing.gossip.lib.models.UserMessage; import dev.wiing.gossip.lib.models.Topic; import dev.wiing.gossip.lib.packets.MessagePushPacket; import dev.wiing.gossip.lib.packets.TopicJoinPacket; @@ -17,6 +19,7 @@ import javafx.scene.control.TextArea; import javafx.scene.input.MouseEvent; import javafx.scene.layout.VBox; +import java.beans.PropertyChangeEvent; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -81,7 +84,13 @@ public class MainChatController { if (evt.getPropertyName().equals("messageAdd")) { Platform.runLater(() -> { - addMessage((Message)evt.getNewValue()); + if (evt.getNewValue() instanceof UserMessage) { + addMessage((UserMessage)evt.getNewValue()); + } + + else if (evt.getNewValue() instanceof SystemMessage) { + addSystemMessage((SystemMessage)evt.getNewValue()); + } }); } }); @@ -113,18 +122,28 @@ public class MainChatController { vboxVisitPage.setVisible(!participating); } - private void addMessage(Message message) { - if (latestMessageItem == null || latestMessageItem.getAuthor().getUserID() != message.getAuthor().getUserID()) { + private void addMessage(UserMessage userMessage) { + if (latestMessageItem == null || latestMessageItem.getAuthor().getUserID() != userMessage.getAuthor().getUserID()) { var newPair = MessageItemController.createInstance(); - newPair.second().setAuthor(message.getAuthor()); + newPair.second().setAuthor(userMessage.getAuthor()); vboxMessages.getChildren().add(newPair.first()); latestMessageItem = newPair.second(); } - latestMessageItem.addMessage(message); + latestMessageItem.addMessage(userMessage); + } + + private void addSystemMessage(SystemMessage systemMessage) { + latestMessageItem = null; + + var pair = SystemMessageItemController.createInstance(); + + pair.second().setSystemMessage(systemMessage); + + vboxMessages.getChildren().add(pair.first()); } @FXML diff --git a/Client/src/main/java/dev/wiing/gossip/client/controllers/MainController.java b/Client/src/main/java/dev/wiing/gossip/client/controllers/MainController.java index e20be92..3c2659a 100644 --- a/Client/src/main/java/dev/wiing/gossip/client/controllers/MainController.java +++ b/Client/src/main/java/dev/wiing/gossip/client/controllers/MainController.java @@ -6,9 +6,7 @@ import dev.wiing.gossip.client.data.UserAvatar; import dev.wiing.gossip.client.generic.Pair; import dev.wiing.gossip.client.utils.Utils; import dev.wiing.gossip.lib.data.LongData; -import dev.wiing.gossip.lib.models.Message; -import dev.wiing.gossip.lib.models.Topic; -import dev.wiing.gossip.lib.models.User; +import dev.wiing.gossip.lib.models.*; import dev.wiing.gossip.lib.packets.*; import javafx.application.Platform; import javafx.event.ActionEvent; @@ -20,6 +18,7 @@ import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; +import java.net.SocketException; import java.net.URL; import java.util.Map; import java.util.ResourceBundle; @@ -56,6 +55,25 @@ public class MainController implements Initializable { UserAvatar.getAvatar(user.getAvatarID()).applyToRegionBackground(paneIcon, true); + TopicListFetchPacket topicListFetchPacket = new TopicListFetchPacket(); + Connection.getInstance().sendPacket(topicListFetchPacket); + + try { + Packet topicListFetchResult = Connection.getInstance().nextPacket(TopicListDataPacket.TYPE); + if (topicListFetchResult != null) { + TopicListDataPacket topicListData = (TopicListDataPacket) topicListFetchResult; + for (LongData topicID : topicListData.getTopicIDs()) { + Topic topic = Connection.getInstance().getTopic(topicID.getValue()); + + if (topic == null) continue; + + addTopic(topic); + } + } + } catch (SocketException e) { + throw new RuntimeException(e); + } + Connection.getInstance().getPacketHandler().addListener(TopicCreatedPacket.class, packet -> { User host = Connection.getInstance().getUser(packet.getHostID()); @@ -67,29 +85,7 @@ public class MainController implements Initializable { packet.getTopicColor() ); - var pair = TopicItemController.createInstance(); - pair.second().setTopic(topic); - - var contentPair = MainChatController.createInstance(); - contentPair.second().setTopic(topic); - - topicMap.put(topic.getId(), contentPair); - - AnchorPane.setLeftAnchor(contentPair.first(), 0.0); - AnchorPane.setBottomAnchor(contentPair.first(), 0.0); - AnchorPane.setTopAnchor(contentPair.first(), 0.0); - AnchorPane.setRightAnchor(contentPair.first(), 0.0); - contentPair.first().setVisible(false); - - Platform.runLater(() -> { - paneContent.getChildren().add(contentPair.first()); - - vboxTopics.getChildren().add(pair.first()); - }); - - pair.second().setOnMouseClicked(event -> { - setActiveTopic(topic, pair.first()); - }); + addTopic(topic); }); Connection.getInstance().getPacketHandler().addListener(TopicUpdatePacket.class, packet -> { @@ -122,9 +118,24 @@ public class MainController implements Initializable { User author = Connection.getInstance().getUser(packet.getAuthorID()); - Message message = new Message(author, topic, packet.getContents()); + UserMessage userMessage = new UserMessage(packet.getMessageID(), author, topic, packet.getContents()); - topic.addMessage(message); + topic.addMessage(userMessage); + }); + + Connection.getInstance().getPacketHandler().addListener(MessageSystemPacket.class, packet -> { + if (!topicMap.containsKey(packet.getTopicID())) return; + + Topic topic = topicMap.get(packet.getTopicID()).second().getTopic(); + + User targetUser = null; + if (packet.getUserID() != 0) { + targetUser = Connection.getInstance().getUser(packet.getUserID()); + } + + SystemMessage systemMessage = new SystemMessage(packet.getMessageID(), topic, packet.getSystemType(), targetUser, packet.getContent()); + + topic.addMessage(systemMessage); }); Connection.getInstance().beginHandlingPackets(); @@ -132,6 +143,90 @@ public class MainController implements Initializable { vboxTopics.getChildren().clear(); } + private void fetchTopicMessage(Topic topic, long messageID) { + MessageFetchPacket messageFetchPacket = new MessageFetchPacket(); + messageFetchPacket.setMessageID(messageID); + messageFetchPacket.setTopicID(topic.getId()); + Connection.getInstance().sendPacket(messageFetchPacket); + + try { + Packet messageFetchResult = Connection.getInstance().nextPacket(MessageDataPacket.TYPE); + if (messageFetchResult == null) return; + + MessageDataPacket messageData = (MessageDataPacket) messageFetchResult; + + Message message = null; + if (messageData.isUserMessage()) { + message = new UserMessage( + messageData.getMessageID(), + Connection.getInstance().getUser(messageData.getUserAuthorID()), + Connection.getInstance().getTopic(messageData.getTopicID()), + messageData.getUserContents() + ); + } + else if (messageData.isSystemMessage()) { + message = new SystemMessage( + messageData.getMessageID(), + Connection.getInstance().getTopic(messageData.getTopicID()), + messageData.getSystemType(), + Connection.getInstance().getUser(messageData.getSystemUserID()), + messageData.getSystemContents() + ); + } + + if (message != null) { + message.getTopic().addMessage(message); + } + + } catch (SocketException e) { + throw new RuntimeException(e); + } + } + + public void addTopic(Topic topic) { + var pair = TopicItemController.createInstance(); + pair.second().setTopic(topic); + + var contentPair = MainChatController.createInstance(); + contentPair.second().setTopic(topic); + + topicMap.put(topic.getId(), contentPair); + + AnchorPane.setLeftAnchor(contentPair.first(), 0.0); + AnchorPane.setBottomAnchor(contentPair.first(), 0.0); + AnchorPane.setTopAnchor(contentPair.first(), 0.0); + AnchorPane.setRightAnchor(contentPair.first(), 0.0); + contentPair.first().setVisible(false); + + MessageListFetchPacket messageListFetchPacket = new MessageListFetchPacket(); + messageListFetchPacket.setTopicID(topic.getId()); + Connection.getInstance().sendPacket(messageListFetchPacket); + + Platform.runLater(() -> { + paneContent.getChildren().add(contentPair.first()); + + vboxTopics.getChildren().add(pair.first()); + }); + + pair.second().setOnMouseClicked(event -> { + setActiveTopic(topic, pair.first()); + }); + + try { + Packet messageListFetchResult = Connection.getInstance().nextPacket(MessageListDataPacket.TYPE); + if (messageListFetchResult == null) return; + + MessageListDataPacket messageListDataPacket = (MessageListDataPacket) messageListFetchResult; + + for (LongData messageID : messageListDataPacket.getMessageIDs()) { + fetchTopicMessage(topic, messageID.getValue()); + } + + } catch (SocketException e) { + throw new RuntimeException(e); + } + } + public void setActiveTopic(Topic topic, Parent element) { if (activeTopic != null) { activeTopicElement.getStyleClass().remove("tag-min"); diff --git a/Client/src/main/java/dev/wiing/gossip/client/controllers/item/MessageItemController.java b/Client/src/main/java/dev/wiing/gossip/client/controllers/item/MessageItemController.java index 10427de..1a5a045 100644 --- a/Client/src/main/java/dev/wiing/gossip/client/controllers/item/MessageItemController.java +++ b/Client/src/main/java/dev/wiing/gossip/client/controllers/item/MessageItemController.java @@ -4,7 +4,7 @@ import dev.wiing.gossip.client.Connection; import dev.wiing.gossip.client.data.UserAvatar; import dev.wiing.gossip.client.generic.Pair; import dev.wiing.gossip.client.utils.Utils; -import dev.wiing.gossip.lib.models.Message; +import dev.wiing.gossip.lib.models.UserMessage; import dev.wiing.gossip.lib.models.User; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -22,7 +22,7 @@ import java.util.ResourceBundle; public class MessageItemController implements Initializable { private User author; - private final List messages = Collections.synchronizedList(new ArrayList<>()); + private final List userMessages = Collections.synchronizedList(new ArrayList<>()); @FXML private Label lblAuthor; @@ -61,24 +61,24 @@ public class MessageItemController implements Initializable { UserAvatar.getAvatar(this.author.getAvatarID()).applyToRegionBackground(paneIcon, true); } - public void addMessage(Message message) { - if (message.getAuthor() != author) return; + public void addMessage(UserMessage userMessage) { + if (userMessage.getAuthor() != author) return; - if (!this.lblTag.isVisible() && messages.isEmpty() && message.getTopic().getHost().getUserID() == message.getAuthor().getUserID()) { + if (!this.lblTag.isVisible() && userMessages.isEmpty() && userMessage.getTopic().getHost().getUserID() == userMessage.getAuthor().getUserID()) { this.lblTag.setVisible(true); this.lblTag.setText("Host"); this.lblTag.getStyleClass().add("secondary"); } - messages.add(message); + userMessages.add(userMessage); - Label label = new Label(message.getContents()); + Label label = new Label(userMessage.getContents()); vboxMessages.getChildren().add(label); } - public List getMessageReadOnly() { - return Collections.unmodifiableList(messages); + public List getMessageReadOnly() { + return Collections.unmodifiableList(userMessages); } public static Pair createInstance() { diff --git a/Client/src/main/java/dev/wiing/gossip/client/controllers/item/SystemMessageItemController.java b/Client/src/main/java/dev/wiing/gossip/client/controllers/item/SystemMessageItemController.java new file mode 100644 index 0000000..9dbedf8 --- /dev/null +++ b/Client/src/main/java/dev/wiing/gossip/client/controllers/item/SystemMessageItemController.java @@ -0,0 +1,76 @@ +package dev.wiing.gossip.client.controllers.item; + +import dev.wiing.gossip.client.generic.Pair; +import dev.wiing.gossip.client.utils.Utils; +import dev.wiing.gossip.lib.models.SystemMessage; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; + +public class SystemMessageItemController { + + private SystemMessage systemMessage; + + @FXML + private ImageView imgIcon; + + @FXML + private Label lblPrefix; + + @FXML + private Label lblSuffix; + + @FXML + private Label lblTimeAgo; + + @FXML + private Label lblContent; + + public void setSystemMessage(SystemMessage systemMessage) { + this.systemMessage = systemMessage; + + switch (this.systemMessage.getType()) { + case SystemMessage.SystemType.USER_JOIN: + removeNode(lblPrefix); + lblContent.setText(systemMessage.getUser().getUsernameDisplay()); + lblSuffix.setText("joined the Topic"); + break; + + case SystemMessage.SystemType.USER_LEAVE: + removeNode(lblPrefix); + lblContent.setText(systemMessage.getUser().getUsernameDisplay()); + lblSuffix.setText("left the Topic"); + break; + + case SystemMessage.SystemType.NAME_CHANGE: + lblPrefix.setText("Topic changed to"); + lblContent.setText(systemMessage.getContents()); + removeNode(lblSuffix); + break; + + case SystemMessage.SystemType.DESCRIPTION_CHANGE: + lblPrefix.setText("Topic Description changed"); + removeNode(lblContent); + removeNode(lblSuffix); + break; + + case SystemMessage.SystemType.HOST_CHANGE: + lblPrefix.setText("Host changed to"); + lblContent.setText(systemMessage.getUser().getUsernameDisplay()); + removeNode(lblSuffix); + break; + } + } + + private void removeNode(Node node) { + ((Pane)node.getParent()).getChildren().remove(node); + } + + public static Pair createInstance() { + return Utils.createInstance("views/system-message-item.fxml"); + } + +} diff --git a/Client/src/main/resources/dev/wiing/gossip/client/views/chat-view.fxml b/Client/src/main/resources/dev/wiing/gossip/client/views/chat-view.fxml index eeab6d6..d1b9f87 100644 --- a/Client/src/main/resources/dev/wiing/gossip/client/views/chat-view.fxml +++ b/Client/src/main/resources/dev/wiing/gossip/client/views/chat-view.fxml @@ -12,11 +12,10 @@ - - + @@ -59,63 +58,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Client/src/main/resources/dev/wiing/gossip/client/views/system-message-item.fxml b/Client/src/main/resources/dev/wiing/gossip/client/views/system-message-item.fxml new file mode 100644 index 0000000..34423cf --- /dev/null +++ b/Client/src/main/resources/dev/wiing/gossip/client/views/system-message-item.fxml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/PacketHandler.java b/Lib/src/main/java/dev/wiing/gossip/lib/PacketHandler.java index ffd100b..9161141 100644 --- a/Lib/src/main/java/dev/wiing/gossip/lib/PacketHandler.java +++ b/Lib/src/main/java/dev/wiing/gossip/lib/PacketHandler.java @@ -1,9 +1,6 @@ package dev.wiing.gossip.lib; -import dev.wiing.gossip.lib.packets.MessageCreatedPacket; -import dev.wiing.gossip.lib.packets.Packet; -import dev.wiing.gossip.lib.packets.TopicCreatedPacket; -import dev.wiing.gossip.lib.packets.TopicUpdatePacket; +import dev.wiing.gossip.lib.packets.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -42,17 +39,14 @@ public class PacketHandler { } public boolean runPacket(Packet packet) { - switch (packet.getType()) { - case TopicCreatedPacket.TYPE: - return runPacket(TopicCreatedPacket.class, (TopicCreatedPacket)packet); - - case TopicUpdatePacket.TYPE: - return runPacket(TopicUpdatePacket.class, (TopicUpdatePacket)packet); - - case MessageCreatedPacket.TYPE: - return runPacket(MessageCreatedPacket.class, (MessageCreatedPacket)packet); - } - return false; + return switch (packet.getType()) { + case TopicCreatedPacket.TYPE -> runPacket(TopicCreatedPacket.class, (TopicCreatedPacket) packet); + case TopicUpdatePacket.TYPE -> runPacket(TopicUpdatePacket.class, (TopicUpdatePacket) packet); + case TopicListDataPacket.TYPE -> runPacket(TopicListDataPacket.class, (TopicListDataPacket) packet); + case MessageCreatedPacket.TYPE -> runPacket(MessageCreatedPacket.class, (MessageCreatedPacket) packet); + case MessageSystemPacket.TYPE -> runPacket(MessageSystemPacket.class, (MessageSystemPacket) packet); + default -> false; + }; } public Set> getListeners(Class packetClass) { @@ -69,7 +63,9 @@ public class PacketHandler { { add(TopicCreatedPacket.TYPE); add(TopicUpdatePacket.TYPE); + add(TopicListDataPacket.TYPE); add(MessageCreatedPacket.TYPE); + add(MessageSystemPacket.TYPE); } }; } diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/PacketManager.java b/Lib/src/main/java/dev/wiing/gossip/lib/PacketManager.java index 3dddd60..a3c85ec 100644 --- a/Lib/src/main/java/dev/wiing/gossip/lib/PacketManager.java +++ b/Lib/src/main/java/dev/wiing/gossip/lib/PacketManager.java @@ -23,12 +23,21 @@ public class PacketManager { add(new TopicCreatedPacket()); add(new TopicJoinPacket()); add(new TopicUpdatePacket()); + add(new TopicListFetchPacket()); + add(new TopicListDataPacket()); + add(new TopicFetchPacket()); + add(new TopicDataPacket()); add(new UserFetchPacket()); add(new UserDataPacket()); add(new MessagePushPacket()); add(new MessageCreatedPacket()); + add(new MessageSystemPacket()); + add(new MessageListFetchPacket()); + add(new MessageListDataPacket()); + add(new MessageFetchPacket()); + add(new MessageDataPacket()); } }; @@ -49,7 +58,7 @@ public class PacketManager { } } - public Packet readPacket(BufferedInputStream stream) throws IOException { + public Packet readPacket(InputStream stream) throws IOException { ByteBuffer buffer = ByteBuffer.wrap(stream.readNBytes(6)); if (buffer.capacity() < 6) return null; @@ -64,10 +73,6 @@ public class PacketManager { return packet.readBytes(ByteBuffer.wrap(stream.readNBytes(size)), size); } - public Packet readPacket(InputStream stream) throws IOException { - return readPacket(new BufferedInputStream(stream)); - } - public void writePacket(BufferedOutputStream stream, Packet packet) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(packet.getTotalLength()); @@ -77,7 +82,6 @@ public class PacketManager { packet.writeBytes(buffer); stream.write(buffer.array()); - stream.flush(); } public void writePacket(OutputStream stream, Packet packet) throws IOException { @@ -86,6 +90,14 @@ public class PacketManager { outputStream.flush(); } + public void writeAllPackets(OutputStream stream, Packet ...packets) throws IOException { + BufferedOutputStream outputStream = new BufferedOutputStream(stream); + for (Packet packet : packets) { + writePacket(outputStream, packet); + } + outputStream.flush(); + } + public void replyPacket(Packet source, Packet reply) throws IOException { BufferedOutputStream outputStream = new BufferedOutputStream(source.getSource().getOutputStream()); writePacket(outputStream, reply); diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/models/Message.java b/Lib/src/main/java/dev/wiing/gossip/lib/models/Message.java index 13224ec..e8a5b69 100644 --- a/Lib/src/main/java/dev/wiing/gossip/lib/models/Message.java +++ b/Lib/src/main/java/dev/wiing/gossip/lib/models/Message.java @@ -3,34 +3,32 @@ package dev.wiing.gossip.lib.models; import java.time.LocalDateTime; public class Message { - private final User author; + private long id; private final Topic topic; - private final String contents; private final LocalDateTime postTime; - public Message(User author, Topic topic, String contents, LocalDateTime postTime) { - this.author = author; + public Message(long id, Topic topic, LocalDateTime postTime) { + this.id = id; this.topic = topic; - this.contents = contents; this.postTime = postTime; } - public Message(User author, Topic topic, String contents) { - this(author, topic, contents, LocalDateTime.now()); + public Message(long id, Topic topic) { + this(id, topic, LocalDateTime.now()); } - public User getAuthor() { - return author; + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; } public Topic getTopic() { return topic; } - public String getContents() { - return contents; - } - public LocalDateTime getPostTime() { return postTime; } diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/models/SystemMessage.java b/Lib/src/main/java/dev/wiing/gossip/lib/models/SystemMessage.java new file mode 100644 index 0000000..566c5f2 --- /dev/null +++ b/Lib/src/main/java/dev/wiing/gossip/lib/models/SystemMessage.java @@ -0,0 +1,43 @@ +package dev.wiing.gossip.lib.models; + +import java.time.LocalDateTime; +import java.util.Objects; + +public class SystemMessage extends Message { + + public static class SystemType { + public static final short USER_JOIN = 1; + public static final short USER_LEAVE = 2; + public static final short NAME_CHANGE = 3; + public static final short DESCRIPTION_CHANGE = 4; + public static final short HOST_CHANGE = 5; + public static final short EXPIRE = 10; + } + + private final short type; + private final User user; + private final String contents; + + public SystemMessage(long id, Topic topic, short type, User user, String contents, LocalDateTime postTime) { + super(id, topic, postTime); + this.type = type; + this.user = user; + this.contents = contents; + } + + public SystemMessage(long id, Topic topic, short type, User user, String contents) { + this(id, topic, type, user, contents, LocalDateTime.now()); + } + + public short getType() { + return type; + } + + public User getUser() { + return user; + } + + public String getContents() { + return contents; + } +} diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/models/Topic.java b/Lib/src/main/java/dev/wiing/gossip/lib/models/Topic.java index 27b59b8..80dd3ca 100644 --- a/Lib/src/main/java/dev/wiing/gossip/lib/models/Topic.java +++ b/Lib/src/main/java/dev/wiing/gossip/lib/models/Topic.java @@ -16,6 +16,9 @@ public class Topic { private short color; private final List messages = Collections.synchronizedList(new ArrayList<>()); + private final Map messageByIDs = new ConcurrentHashMap<>(); + + private long messageTrackerID = 1; public Topic(long id, String name, String description, User host, short color) { this.id = id; @@ -93,10 +96,19 @@ public class Topic { public void addMessage(Message message) { messages.add(message); + messageByIDs.put(message.getId(), message); this.changeSupport.firePropertyChange("messageAdd", null, message); } + public Message getMessageByID(long messageID) { + return messageByIDs.getOrDefault(messageID, null); + } + + public long getNextMessageID() { + return messageTrackerID++; + } + public void addChangeListener(PropertyChangeListener listener) { this.changeSupport.addPropertyChangeListener(listener); } diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/models/UserMessage.java b/Lib/src/main/java/dev/wiing/gossip/lib/models/UserMessage.java new file mode 100644 index 0000000..c54d0df --- /dev/null +++ b/Lib/src/main/java/dev/wiing/gossip/lib/models/UserMessage.java @@ -0,0 +1,26 @@ +package dev.wiing.gossip.lib.models; + +import java.time.LocalDateTime; + +public class UserMessage extends Message { + private final User author; + private final String contents; + + public UserMessage(long id, User author, Topic topic, String contents, LocalDateTime postTime) { + super(id, topic, postTime); + this.author = author; + this.contents = contents; + } + + public UserMessage(long id, User author, Topic topic, String contents) { + this(id, author, topic, contents, LocalDateTime.now()); + } + + public User getAuthor() { + return author; + } + + public String getContents() { + return contents; + } +} diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageCreatedPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageCreatedPacket.java index 66a11c6..9fdcbb3 100644 --- a/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageCreatedPacket.java +++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageCreatedPacket.java @@ -7,8 +7,9 @@ import java.nio.ByteBuffer; public class MessageCreatedPacket extends Packet { public static final short TYPE = 0x32; - public static final int LENGTH = 0x014; + public static final int LENGTH = 0x01C; + private long messageID; private long authorID; private long topicID; private final StringData contents = new StringData(); @@ -17,6 +18,14 @@ public class MessageCreatedPacket extends Packet { super(TYPE, LENGTH); } + public long getMessageID() { + return messageID; + } + + public void setMessageID(long messageID) { + this.messageID = messageID; + } + public long getAuthorID() { return authorID; } @@ -39,7 +48,7 @@ public class MessageCreatedPacket extends Packet { public void setContents(String contents) { this.contents.setValue(contents); - setLength(16 + this.contents.getLength()); + setLength(24 + this.contents.getLength()); } @Override @@ -47,6 +56,7 @@ public class MessageCreatedPacket extends Packet { MessageCreatedPacket packet = new MessageCreatedPacket(); packet.setLength(size); + packet.messageID = buffer.getLong(); packet.authorID = buffer.getLong(); packet.topicID = buffer.getLong(); packet.contents.setBytes(buffer); @@ -56,6 +66,7 @@ public class MessageCreatedPacket extends Packet { @Override public void writeBytes(ByteBuffer buffer) { + buffer.putLong(messageID); buffer.putLong(authorID); buffer.putLong(topicID); buffer.put(contents.getBytes()); diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageDataPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageDataPacket.java new file mode 100644 index 0000000..ea9a234 --- /dev/null +++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageDataPacket.java @@ -0,0 +1,151 @@ +package dev.wiing.gossip.lib.packets; + +import dev.wiing.gossip.lib.data.StringData; + +import java.nio.ByteBuffer; + +public class MessageDataPacket extends Packet { + + public static final short TYPE = 0x37; + public static final int LENGTH = 0x000; + + public static class MessageType { + public static final byte USER = 1; + public static final byte SYSTEM = 2; + } + + private long messageID; + private long topicID; + + private byte messageType; + + // USER TYPE + private long userAuthorID; + private final StringData userContents = new StringData(); + + // SYSTEM TYPE + private short systemType; + private long systemUserID = 0; + private final StringData systemContents = new StringData(); + + public MessageDataPacket() { + super(TYPE, LENGTH); + } + + private void updateLength() { + setLength(17 + + (isUserMessage() ? (8 + userContents.getLength()) : 0) + + (isSystemMessage() ? (16 + systemContents.getLength()) : 0)); + } + + public boolean isUserMessage() { + return messageType == MessageType.USER; + } + + public boolean isSystemMessage() { + return messageType == MessageType.SYSTEM; + } + + public long getMessageID() { + return messageID; + } + + public void setMessageID(long messageID) { + this.messageID = messageID; + } + + public long getTopicID() { + return topicID; + } + + public void setTopicID(long topicID) { + this.topicID = topicID; + } + + public byte getMessageType() { + return messageType; + } + + public void setMessageType(byte messageType) { + this.messageType = messageType; + } + + public long getUserAuthorID() { + return userAuthorID; + } + + public void setUserAuthorID(long userAuthorID) { + this.userAuthorID = userAuthorID; + } + + public String getUserContents() { + return userContents.getValue(); + } + + public void setUserContents(String userContents) { + this.userContents.setValue(userContents); + updateLength(); + } + + public short getSystemType() { + return systemType; + } + + public void setSystemType(short systemType) { + this.systemType = systemType; + } + + public long getSystemUserID() { + return systemUserID; + } + + public void setSystemUserID(long systemUserID) { + this.systemUserID = systemUserID; + } + + public String getSystemContents() { + return systemContents.getValue(); + } + + public void setSystemContents(String systemContents) { + this.systemContents.setValue(systemContents); + updateLength(); + } + + @Override + public Packet readBytes(ByteBuffer buffer, int size) { + MessageDataPacket packet = new MessageDataPacket(); + packet.setLength(size); + + packet.messageID = buffer.getLong(); + packet.topicID = buffer.getLong(); + packet.messageType = buffer.get(); + + if (packet.isUserMessage()) { + packet.userAuthorID = buffer.getLong(); + packet.userContents.setBytes(buffer); + } else if (packet.isSystemMessage()) { + packet.systemType = buffer.getShort(); + packet.systemUserID = buffer.getLong(); + packet.systemContents.setBytes(buffer); + } + + return packet; + } + + @Override + public void writeBytes(ByteBuffer buffer) { + buffer.putLong(messageID); + buffer.putLong(topicID); + buffer.put(messageType); + + if (isUserMessage()) { + buffer.putLong(userAuthorID); + buffer.put(userContents.getBytes()); + } else if (isSystemMessage()) { + buffer.putShort(systemType); + buffer.putLong(systemUserID); + buffer.put(systemContents.getBytes()); + } + } +} diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageFetchPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageFetchPacket.java new file mode 100644 index 0000000..97ca4fd --- /dev/null +++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageFetchPacket.java @@ -0,0 +1,48 @@ +package dev.wiing.gossip.lib.packets; + +import java.nio.ByteBuffer; + +public class MessageFetchPacket extends Packet { + + public static final short TYPE = 0x36; + public static final int LENGTH = 0x010; + + private long topicID; + private long messageID; + + public MessageFetchPacket() { + super(TYPE, LENGTH); + } + + public long getTopicID() { + return topicID; + } + + public void setTopicID(long topicID) { + this.topicID = topicID; + } + + public long getMessageID() { + return messageID; + } + + public void setMessageID(long messageID) { + this.messageID = messageID; + } + + @Override + public Packet readBytes(ByteBuffer buffer, int size) { + MessageFetchPacket packet = new MessageFetchPacket(); + + packet.topicID = buffer.getLong(); + packet.messageID = buffer.getLong(); + + return packet; + } + + @Override + public void writeBytes(ByteBuffer buffer) { + buffer.putLong(topicID); + buffer.putLong(messageID); + } +} diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageListDataPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageListDataPacket.java new file mode 100644 index 0000000..76e83a0 --- /dev/null +++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageListDataPacket.java @@ -0,0 +1,59 @@ +package dev.wiing.gossip.lib.packets; + +import dev.wiing.gossip.lib.data.ListData; +import dev.wiing.gossip.lib.data.LongData; + +import java.nio.ByteBuffer; +import java.util.List; + +public class MessageListDataPacket extends Packet { + + public static final short TYPE = 0x35; + public static final int LENGTH = 0x00C; + + private long topicID; + private final ListData messageIDs = new ListData<>(LongData::new); + + public MessageListDataPacket() { + super(TYPE, LENGTH); + } + + private void updateLength() { + setLength(8 + messageIDs.getLength()); + } + + @Override + public int getLength() { + updateLength(); + return super.getLength(); + } + + public long getTopicID() { + return topicID; + } + + public void setTopicID(long topicID) { + this.topicID = topicID; + } + + public List getMessageIDs() { + return messageIDs.getData(); + } + + @Override + public Packet readBytes(ByteBuffer buffer, int size) { + MessageListDataPacket packet = new MessageListDataPacket(); + packet.setLength(size); + + packet.topicID = buffer.getLong(); + packet.messageIDs.setBytes(buffer); + + return packet; + } + + @Override + public void writeBytes(ByteBuffer buffer) { + buffer.putLong(topicID); + buffer.put(messageIDs.getBytes()); + } +} diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageListFetchPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageListFetchPacket.java new file mode 100644 index 0000000..232c66c --- /dev/null +++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageListFetchPacket.java @@ -0,0 +1,37 @@ +package dev.wiing.gossip.lib.packets; + +import java.nio.ByteBuffer; + +public class MessageListFetchPacket extends Packet { + + public static final short TYPE = 0x34; + public static final int LENGTH = 0x008; + + private long topicID; + + public MessageListFetchPacket() { + super(TYPE, LENGTH); + } + + public long getTopicID() { + return topicID; + } + + public void setTopicID(long topicID) { + this.topicID = topicID; + } + + @Override + public Packet readBytes(ByteBuffer buffer, int size) { + MessageListFetchPacket packet = new MessageListFetchPacket(); + + packet.topicID = buffer.getLong(); + + return packet; + } + + @Override + public void writeBytes(ByteBuffer buffer) { + buffer.putLong(topicID); + } +} diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageSystemPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageSystemPacket.java new file mode 100644 index 0000000..0992e42 --- /dev/null +++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/MessageSystemPacket.java @@ -0,0 +1,86 @@ +package dev.wiing.gossip.lib.packets; + +import dev.wiing.gossip.lib.data.StringData; + +import java.nio.ByteBuffer; +import java.util.Objects; + +public class MessageSystemPacket extends Packet { + + public static final short TYPE = 0x33; + public static final int LENGTH = 0x01E; + + private long messageID; + private long topicID; + private short systemType = 0; + private long userID = 0; + private final StringData content = new StringData(); + + public MessageSystemPacket() { + super(TYPE, LENGTH); + } + + public long getMessageID() { + return messageID; + } + + public void setMessageID(long messageID) { + this.messageID = messageID; + } + + public long getTopicID() { + return topicID; + } + + public void setTopicID(long topicID) { + this.topicID = topicID; + } + + public short getSystemType() { + return systemType; + } + + public void setSystemType(short systemType) { + this.systemType = systemType; + } + + public long getUserID() { + return userID; + } + + public void setUserID(long userID) { + this.userID = userID; + } + + public String getContent() { + return content.getValue(); + } + + public void setContent(String content) { + this.content.setValue(content); + setLength(26 + this.content.getLength()); + } + + @Override + public Packet readBytes(ByteBuffer buffer, int size) { + MessageSystemPacket packet = new MessageSystemPacket(); + packet.setLength(size); + + packet.messageID = buffer.getLong(); + packet.topicID = buffer.getLong(); + packet.systemType = buffer.getShort(); + packet.userID = buffer.getLong(); + packet.content.setBytes(buffer); + + return packet; + } + + @Override + public void writeBytes(ByteBuffer buffer) { + buffer.putLong(messageID); + buffer.putLong(topicID); + buffer.putShort(systemType); + buffer.putLong(userID); + buffer.put(content.getBytes()); + } +} diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicDataPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicDataPacket.java new file mode 100644 index 0000000..8d9a73d --- /dev/null +++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicDataPacket.java @@ -0,0 +1,109 @@ +package dev.wiing.gossip.lib.packets; + +import dev.wiing.gossip.lib.data.ListData; +import dev.wiing.gossip.lib.data.LongData; +import dev.wiing.gossip.lib.data.StringData; + +import java.nio.ByteBuffer; +import java.util.List; + +public class TopicDataPacket extends Packet { + + public static final short TYPE = 0x18; + public static final int LENGTH = 0x01E; + + private long topicID; + private long hostID; + private final StringData topicName = new StringData(); + private final StringData topicDescription = new StringData(); + private short topicColor; + private final ListData userIDs = new ListData<>(LongData::new); + + public TopicDataPacket() { + super(TYPE, LENGTH); + } + + private void updateLength() { + super.setLength(18 + + topicName.getLength() + + topicDescription.getLength() + + userIDs.getLength()); + } + + @Override + public int getLength() { + updateLength(); + return super.getLength(); + } + + public long getTopicID() { + return topicID; + } + + public void setTopicID(long topicID) { + this.topicID = topicID; + } + + public long getHostID() { + return hostID; + } + + public void setHostID(long hostID) { + this.hostID = hostID; + } + + public String getTopicName() { + return topicName.getValue(); + } + + public void setTopicName(String topicName) { + this.topicName.setValue(topicName); + updateLength(); + } + + public String getTopicDescription() { + return topicDescription.getValue(); + } + + public void setTopicDescription(String topicDescription) { + this.topicDescription.setValue(topicDescription); + updateLength(); + } + + public short getTopicColor() { + return topicColor; + } + + public void setTopicColor(short topicColor) { + this.topicColor = topicColor; + } + + public List getUserIDs() { + return userIDs.getData(); + } + + @Override + public Packet readBytes(ByteBuffer buffer, int size) { + TopicDataPacket packet = new TopicDataPacket(); + packet.setLength(size); + + packet.topicID = buffer.getLong(); + packet.hostID = buffer.getLong(); + packet.topicName.setBytes(buffer); + packet.topicDescription.setBytes(buffer); + packet.topicColor = buffer.getShort(); + packet.userIDs.setBytes(buffer); + + return packet; + } + + @Override + public void writeBytes(ByteBuffer buffer) { + buffer.putLong(topicID); + buffer.putLong(hostID); + buffer.put(topicName.getBytes()); + buffer.put(topicDescription.getBytes()); + buffer.putShort(topicColor); + buffer.put(userIDs.getBytes()); + } +} diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicFetchPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicFetchPacket.java new file mode 100644 index 0000000..4b951a5 --- /dev/null +++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicFetchPacket.java @@ -0,0 +1,37 @@ +package dev.wiing.gossip.lib.packets; + +import java.nio.ByteBuffer; + +public class TopicFetchPacket extends Packet { + + public static final short TYPE = 0x17; + public static final int LENGTH = 8; + + private long topicID; + + public TopicFetchPacket() { + super(TYPE, LENGTH); + } + + public long getTopicID() { + return topicID; + } + + public void setTopicID(long topicID) { + this.topicID = topicID; + } + + @Override + public Packet readBytes(ByteBuffer buffer, int size) { + TopicFetchPacket packet = new TopicFetchPacket(); + + packet.topicID = buffer.getLong(); + + return packet; + } + + @Override + public void writeBytes(ByteBuffer buffer) { + buffer.putLong(topicID); + } +} diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicListDataPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicListDataPacket.java new file mode 100644 index 0000000..e47cee2 --- /dev/null +++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicListDataPacket.java @@ -0,0 +1,48 @@ +package dev.wiing.gossip.lib.packets; + +import dev.wiing.gossip.lib.data.ListData; +import dev.wiing.gossip.lib.data.LongData; + +import java.nio.ByteBuffer; +import java.util.List; + +public class TopicListDataPacket extends Packet { + + public static final short TYPE = 0x16; + public static final int LENGTH = 0x004; + + private final ListData topicIDs = new ListData<>(LongData::new); + + public TopicListDataPacket() { + super(TYPE, LENGTH); + } + + private void updateLength() { + setLength(this.topicIDs.getLength()); + } + + @Override + public int getLength() { + updateLength(); + return super.getLength(); + } + + public List getTopicIDs() { + return topicIDs.getData(); + } + + @Override + public Packet readBytes(ByteBuffer buffer, int size) { + TopicListDataPacket packet = new TopicListDataPacket(); + packet.setLength(size); + + packet.topicIDs.setBytes(buffer); + + return packet; + } + + @Override + public void writeBytes(ByteBuffer buffer) { + buffer.put(topicIDs.getBytes()); + } +} diff --git a/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicListFetchPacket.java b/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicListFetchPacket.java new file mode 100644 index 0000000..dae0018 --- /dev/null +++ b/Lib/src/main/java/dev/wiing/gossip/lib/packets/TopicListFetchPacket.java @@ -0,0 +1,26 @@ +package dev.wiing.gossip.lib.packets; + +import java.nio.ByteBuffer; + +public class TopicListFetchPacket extends Packet { + + public static final short TYPE = 0x15; + public static final int LENGTH = 0x000; + + public TopicListFetchPacket() { + super(TYPE, LENGTH); + } + + @Override + public Packet readBytes(ByteBuffer buffer, int size) { + TopicListFetchPacket packet = new TopicListFetchPacket(); + packet.setLength(size); + + return packet; + } + + @Override + public void writeBytes(ByteBuffer buffer) { + + } +} diff --git a/Server/src/main/java/dev/wiing/gossip/server/Database.java b/Server/src/main/java/dev/wiing/gossip/server/Database.java index 56e8916..03dcd22 100644 --- a/Server/src/main/java/dev/wiing/gossip/server/Database.java +++ b/Server/src/main/java/dev/wiing/gossip/server/Database.java @@ -134,4 +134,8 @@ public class Database { logger.info("Topic removed: \"{}\" (#{})", topic.getName(), topic.getId()); } + + public List getAllTopicsReadOnly() { + return Collections.unmodifiableList(topics.values().stream().toList()); + } } diff --git a/Server/src/main/java/dev/wiing/gossip/server/UserSocket.java b/Server/src/main/java/dev/wiing/gossip/server/UserSocket.java index e98c985..1fb8515 100644 --- a/Server/src/main/java/dev/wiing/gossip/server/UserSocket.java +++ b/Server/src/main/java/dev/wiing/gossip/server/UserSocket.java @@ -1,18 +1,15 @@ package dev.wiing.gossip.server; import dev.wiing.gossip.lib.data.LongData; -import dev.wiing.gossip.lib.models.Message; -import dev.wiing.gossip.lib.models.SecretUser; -import dev.wiing.gossip.lib.models.Topic; -import dev.wiing.gossip.lib.models.User; +import dev.wiing.gossip.lib.models.*; import dev.wiing.gossip.lib.packets.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.io.BufferedInputStream; import java.io.IOException; import java.net.Socket; import java.net.SocketException; +import java.util.List; public record UserSocket(Socket socket) implements Runnable { @@ -33,7 +30,7 @@ public record UserSocket(Socket socket) implements Runnable { if (!socket.isConnected()) break; - Packet packet = Globals.getPacketManager().readPacket(new BufferedInputStream(socket.getInputStream())); + Packet packet = Globals.getPacketManager().readPacket(socket.getInputStream()); if (packet == null) continue; @@ -52,6 +49,14 @@ public record UserSocket(Socket socket) implements Runnable { onUserJoinTopic((TopicJoinPacket) packet); break; + case TopicListFetchPacket.TYPE: + onTopicListFetch((TopicListFetchPacket) packet); + break; + + case TopicFetchPacket.TYPE: + onFetchTopic((TopicFetchPacket) packet); + break; + case UserFetchPacket.TYPE: onFetchUser((UserFetchPacket) packet); break; @@ -59,6 +64,14 @@ public record UserSocket(Socket socket) implements Runnable { case MessagePushPacket.TYPE: onUserMessage((MessagePushPacket) packet); break; + + case MessageListFetchPacket.TYPE: + onMessageListFetch((MessageListFetchPacket) packet); + break; + + case MessageFetchPacket.TYPE: + onFetchMessage((MessageFetchPacket) packet); + break; } } catch (SocketException e) { loop = false; @@ -120,10 +133,13 @@ public record UserSocket(Socket socket) implements Runnable { if (requester == null) return; if (topic == null) return; - if (!topic.hasUser(requester)) { - info("\"{}\" joined topic \"{}\" (#{})", topic.getHost().getUsername().toUpperCase(), topic.getName().toUpperCase(), topic.getId()); - topic.addUser(requester); - } + if (topic.hasUser(requester)) return; + + info("\"{}\" joined topic \"{}\" (#{})", topic.getHost().getUsername().toUpperCase(), topic.getName().toUpperCase(), topic.getId()); + topic.addUser(requester); + + SystemMessage message = new SystemMessage(topic.getNextMessageID(), topic, SystemMessage.SystemType.USER_JOIN, requester, ""); + topic.addMessage(message); AckPacket resp = new AckPacket(); resp.setAcknowledgement(packet); @@ -138,17 +154,62 @@ public record UserSocket(Socket socket) implements Runnable { updatePacket.setTopicID(topic.getId()); updatePacket.getUsersJoined().add(new LongData().setValue(requester.getUserID())); + MessageSystemPacket systemPacket = new MessageSystemPacket(); + systemPacket.setTopicID(topic.getId()); + systemPacket.setSystemType(message.getType()); + systemPacket.setUserID(message.getUser().getUserID()); + for (User user : Database.getInstance().getUsers()) { Socket socket = Database.getInstance().getUserSocket(user.getUserID()); try { - Globals.getPacketManager().writePacket(socket.getOutputStream(), updatePacket); + Globals.getPacketManager().writeAllPackets(socket.getOutputStream(), updatePacket, systemPacket); } catch (IOException e) { logger.error(e); } } } + private void onTopicListFetch(TopicListFetchPacket packet) { + TopicListDataPacket result = new TopicListDataPacket(); + + info("Requested Topic list"); + + for (Topic topic : Database.getInstance().getAllTopicsReadOnly()) { + result.getTopicIDs().add(new LongData().setValue(topic.getId())); + } + + try { + Globals.getPacketManager().replyPacket(packet, result); + } catch (IOException e) { + logger.error(e); + } + } + + private void onFetchTopic(TopicFetchPacket packet) { + Topic topic = Database.getInstance().getTopic(packet.getTopicID()); + + if (topic == null) return; + + info("Requested topic #{}", topic.getId()); + + TopicDataPacket resp = new TopicDataPacket(); + resp.setTopicID(topic.getId()); + resp.setHostID(topic.getHost().getUserID()); + resp.setTopicName(topic.getName()); + resp.setTopicDescription(topic.getDescription()); + resp.setTopicColor(topic.getColor()); + for (User value : topic.getUsersReadOnly().values()) { + resp.getUserIDs().add(new LongData().setValue(value.getUserID())); + } + + try { + Globals.getPacketManager().replyPacket(packet, resp); + } catch (IOException e) { + logger.error(e); + } + } + private void onFetchUser(UserFetchPacket packet) { User user = Database.getInstance().getUserByID(packet.getUserID()); @@ -175,9 +236,9 @@ public record UserSocket(Socket socket) implements Runnable { if (user == null) return; if (topic == null) return; - Message message = new Message(user, topic, packet.getMessage()); + UserMessage userMessage = new UserMessage(topic.getNextMessageID(), user, topic, packet.getMessage()); - topic.addMessage(message); + topic.addMessage(userMessage); info("\"{}\" in \"{}\" sends \"{}\"", user.getUsername().toUpperCase(), topic.getName().toUpperCase(), packet.getMessage()); @@ -193,7 +254,7 @@ public record UserSocket(Socket socket) implements Runnable { MessageCreatedPacket messageCreated = new MessageCreatedPacket(); messageCreated.setAuthorID(user.getUserID()); messageCreated.setTopicID(topic.getId()); - messageCreated.setContents(message.getContents()); + messageCreated.setContents(userMessage.getContents()); for (User babbler : topic.getUsersReadOnly().values()) { Socket userSocket = Database.getInstance().getUserSocket(babbler.getUserID()); @@ -206,4 +267,58 @@ public record UserSocket(Socket socket) implements Runnable { } } + private void onMessageListFetch(MessageListFetchPacket packet) { + Topic topic = Database.getInstance().getTopic(packet.getTopicID()); + + if (topic == null) return; + + info("Requested Message list on #{}", topic.getId()); + + MessageListDataPacket result = new MessageListDataPacket(); + + for (Message message : topic.getMessagesReadOnly()) { + result.getMessageIDs().add(new LongData().setValue(message.getId())); + } + + try { + Globals.getPacketManager().replyPacket(packet, result); + } catch (IOException e) { + logger.error(e); + } + } + + private void onFetchMessage(MessageFetchPacket packet) { + Topic topic = Database.getInstance().getTopic(packet.getTopicID()); + + if (topic == null) return; + + Message message = topic.getMessageByID(packet.getMessageID()); + + if (message == null) return; + + info("Requested message #{} on #{}", message.getId(), topic.getId()); + + MessageDataPacket resp = new MessageDataPacket(); + resp.setMessageID(message.getId()); + resp.setTopicID(message.getTopic().getId()); + + if (message instanceof UserMessage userMessage) { + resp.setMessageType(MessageDataPacket.MessageType.USER); + resp.setUserAuthorID(userMessage.getAuthor().getUserID()); + resp.setUserContents(userMessage.getContents()); + } else if (message instanceof SystemMessage systemMessage) { + resp.setMessageType(MessageDataPacket.MessageType.SYSTEM); + resp.setSystemType(systemMessage.getType()); + if (systemMessage.getUser() != null) + resp.setSystemUserID(systemMessage.getUser().getUserID()); + resp.setSystemContents(systemMessage.getContents()); + } + + try { + Globals.getPacketManager().replyPacket(packet, resp); + } catch (IOException e) { + logger.error(e); + } + } + }