Late-pulling

This commit is contained in:
Donatas Kirda 2023-12-13 11:25:41 +02:00
parent 0b1270a916
commit fff9fb90fa
Signed by: bloodwiing
GPG Key ID: 63020D8D3F4A164F
27 changed files with 1260 additions and 193 deletions

View File

@ -2,7 +2,9 @@ package dev.wiing.gossip.client;
import dev.wiing.gossip.lib.PacketHandler; import dev.wiing.gossip.lib.PacketHandler;
import dev.wiing.gossip.lib.PacketManager; 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.SecretUser;
import dev.wiing.gossip.lib.models.Topic;
import dev.wiing.gossip.lib.models.User; import dev.wiing.gossip.lib.models.User;
import dev.wiing.gossip.lib.packets.*; import dev.wiing.gossip.lib.packets.*;
@ -103,9 +105,11 @@ public class Connection {
} }
public Packet nextPacket(boolean useQueue) throws SocketException { public Packet nextPacket(boolean useQueue) throws SocketException {
synchronized (queuedPackets) {
if (useQueue && !queuedPackets.isEmpty()) { if (useQueue && !queuedPackets.isEmpty()) {
return queuedPackets.remove(0); return queuedPackets.remove(0);
} }
}
try { try {
return getPacketManager().readPacket(getSocket().getInputStream()); return getPacketManager().readPacket(getSocket().getInputStream());
@ -137,6 +141,7 @@ public class Connection {
public Packet findPacketOfTypes(List<Short> types) { public Packet findPacketOfTypes(List<Short> types) {
Packet packet = null; Packet packet = null;
synchronized (queuedPackets) {
int i; int i;
for (i = 0; i < queuedPackets.size(); i++) { for (i = 0; i < queuedPackets.size(); i++) {
Packet queuedPacket = queuedPackets.get(i); Packet queuedPacket = queuedPackets.get(i);
@ -154,6 +159,7 @@ public class Connection {
return null; return null;
} }
}
public boolean findAck(short acknowledgement) throws IOException { public boolean findAck(short acknowledgement) throws IOException {
@ -162,6 +168,7 @@ public class Connection {
AckPacket ack = null; AckPacket ack = null;
synchronized (queuedPackets) {
int i; int i;
for (i = 0; i < queuedPackets.size(); i++) { for (i = 0; i < queuedPackets.size(); i++) {
Packet queuedPacket = queuedPackets.get(i); Packet queuedPacket = queuedPackets.get(i);
@ -183,6 +190,7 @@ public class Connection {
} }
} }
} }
}
public SecretUser getSelf() { public SecretUser getSelf() {
return self; return self;
@ -198,17 +206,9 @@ public class Connection {
)); ));
} }
private final Map<Long, User> userCache = new ConcurrentHashMap<>(); private final DataCache<User> userCache = new DataCache<>(id -> {
public User getUser(long userID) {
User result;
if ((result = userCache.getOrDefault(userID, null)) != null) {
return result;
}
UserFetchPacket fetch = new UserFetchPacket(); UserFetchPacket fetch = new UserFetchPacket();
fetch.setUserID(userID); fetch.setUserID(id);
Connection.getInstance().sendPacket(fetch); Connection.getInstance().sendPacket(fetch);
Packet resp; Packet resp;
@ -219,14 +219,50 @@ public class Connection {
} }
if (resp.getType() == UserDataPacket.TYPE) { if (resp.getType() == UserDataPacket.TYPE) {
UserDataPacket userData = (UserDataPacket)resp; UserDataPacket userData = (UserDataPacket)resp;
result = new User( return new User(
userData.getUsername(), userData.getUsername(),
userData.getAvatarID(), userData.getAvatarID(),
userData.getUserID() userData.getUserID()
); );
userCache.put(result.getUserID(), result);
} }
return null;
});
public User getUser(long userID) {
return userCache.get(userID);
}
private final DataCache<Topic> 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 result;
} }
return null;
});
public Topic getTopic(long topicID) {
return topicCache.get(topicID);
}
} }

View File

@ -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<T> {
private final DataFetchFunction<T> fetchFunction;
public DataCache(DataFetchFunction<T> fetchFunction) {
this.fetchFunction = fetchFunction;
}
@FunctionalInterface
public interface DataFetchFunction<U> {
U fetch(long id);
}
private final Map<Long, T> 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);
}
}

View File

@ -13,7 +13,7 @@ public class GossipApp extends Application {
System.setProperty("prism.lcdtext", "false"); System.setProperty("prism.lcdtext", "false");
FXMLLoader fxmlLoader = new FXMLLoader(Program.class.getResource("views/login-view.fxml")); 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.setTitle("GossipApp");
stage.setScene(scene); stage.setScene(scene);

View File

@ -2,9 +2,11 @@ package dev.wiing.gossip.client.controllers;
import dev.wiing.gossip.client.Connection; import dev.wiing.gossip.client.Connection;
import dev.wiing.gossip.client.controllers.item.MessageItemController; 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.generic.Pair;
import dev.wiing.gossip.client.utils.Utils; 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.models.Topic;
import dev.wiing.gossip.lib.packets.MessagePushPacket; import dev.wiing.gossip.lib.packets.MessagePushPacket;
import dev.wiing.gossip.lib.packets.TopicJoinPacket; import dev.wiing.gossip.lib.packets.TopicJoinPacket;
@ -17,6 +19,7 @@ import javafx.scene.control.TextArea;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import java.beans.PropertyChangeEvent;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -81,7 +84,13 @@ public class MainChatController {
if (evt.getPropertyName().equals("messageAdd")) { if (evt.getPropertyName().equals("messageAdd")) {
Platform.runLater(() -> { 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); vboxVisitPage.setVisible(!participating);
} }
private void addMessage(Message message) { private void addMessage(UserMessage userMessage) {
if (latestMessageItem == null || latestMessageItem.getAuthor().getUserID() != message.getAuthor().getUserID()) { if (latestMessageItem == null || latestMessageItem.getAuthor().getUserID() != userMessage.getAuthor().getUserID()) {
var newPair = MessageItemController.createInstance(); var newPair = MessageItemController.createInstance();
newPair.second().setAuthor(message.getAuthor()); newPair.second().setAuthor(userMessage.getAuthor());
vboxMessages.getChildren().add(newPair.first()); vboxMessages.getChildren().add(newPair.first());
latestMessageItem = newPair.second(); 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 @FXML

View File

@ -6,9 +6,7 @@ import dev.wiing.gossip.client.data.UserAvatar;
import dev.wiing.gossip.client.generic.Pair; import dev.wiing.gossip.client.generic.Pair;
import dev.wiing.gossip.client.utils.Utils; import dev.wiing.gossip.client.utils.Utils;
import dev.wiing.gossip.lib.data.LongData; import dev.wiing.gossip.lib.data.LongData;
import dev.wiing.gossip.lib.models.Message; import dev.wiing.gossip.lib.models.*;
import dev.wiing.gossip.lib.models.Topic;
import dev.wiing.gossip.lib.models.User;
import dev.wiing.gossip.lib.packets.*; import dev.wiing.gossip.lib.packets.*;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
@ -20,6 +18,7 @@ import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import java.net.SocketException;
import java.net.URL; import java.net.URL;
import java.util.Map; import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
@ -56,6 +55,25 @@ public class MainController implements Initializable {
UserAvatar.getAvatar(user.getAvatarID()).applyToRegionBackground(paneIcon, true); 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 -> { Connection.getInstance().getPacketHandler().addListener(TopicCreatedPacket.class, packet -> {
User host = Connection.getInstance().getUser(packet.getHostID()); User host = Connection.getInstance().getUser(packet.getHostID());
@ -67,29 +85,7 @@ public class MainController implements Initializable {
packet.getTopicColor() packet.getTopicColor()
); );
var pair = TopicItemController.createInstance(); addTopic(topic);
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());
});
}); });
Connection.getInstance().getPacketHandler().addListener(TopicUpdatePacket.class, packet -> { Connection.getInstance().getPacketHandler().addListener(TopicUpdatePacket.class, packet -> {
@ -122,9 +118,24 @@ public class MainController implements Initializable {
User author = Connection.getInstance().getUser(packet.getAuthorID()); 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(); Connection.getInstance().beginHandlingPackets();
@ -132,6 +143,90 @@ public class MainController implements Initializable {
vboxTopics.getChildren().clear(); 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) { public void setActiveTopic(Topic topic, Parent element) {
if (activeTopic != null) { if (activeTopic != null) {
activeTopicElement.getStyleClass().remove("tag-min"); activeTopicElement.getStyleClass().remove("tag-min");

View File

@ -4,7 +4,7 @@ import dev.wiing.gossip.client.Connection;
import dev.wiing.gossip.client.data.UserAvatar; import dev.wiing.gossip.client.data.UserAvatar;
import dev.wiing.gossip.client.generic.Pair; import dev.wiing.gossip.client.generic.Pair;
import dev.wiing.gossip.client.utils.Utils; 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 dev.wiing.gossip.lib.models.User;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
@ -22,7 +22,7 @@ import java.util.ResourceBundle;
public class MessageItemController implements Initializable { public class MessageItemController implements Initializable {
private User author; private User author;
private final List<Message> messages = Collections.synchronizedList(new ArrayList<>()); private final List<UserMessage> userMessages = Collections.synchronizedList(new ArrayList<>());
@FXML @FXML
private Label lblAuthor; private Label lblAuthor;
@ -61,24 +61,24 @@ public class MessageItemController implements Initializable {
UserAvatar.getAvatar(this.author.getAvatarID()).applyToRegionBackground(paneIcon, true); UserAvatar.getAvatar(this.author.getAvatarID()).applyToRegionBackground(paneIcon, true);
} }
public void addMessage(Message message) { public void addMessage(UserMessage userMessage) {
if (message.getAuthor() != author) return; 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.setVisible(true);
this.lblTag.setText("Host"); this.lblTag.setText("Host");
this.lblTag.getStyleClass().add("secondary"); 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); vboxMessages.getChildren().add(label);
} }
public List<Message> getMessageReadOnly() { public List<UserMessage> getMessageReadOnly() {
return Collections.unmodifiableList(messages); return Collections.unmodifiableList(userMessages);
} }
public static Pair<Parent, MessageItemController> createInstance() { public static Pair<Parent, MessageItemController> createInstance() {

View File

@ -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<Parent, SystemMessageItemController> createInstance() {
return Utils.createInstance("views/system-message-item.fxml");
}
}

View File

@ -12,11 +12,10 @@
<?import javafx.scene.image.ImageView?> <?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?> <?import javafx.scene.shape.Circle?>
<AnchorPane prefHeight="318.0" prefWidth="523.0" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dev.wiing.gossip.client.controllers.MainChatController"> <AnchorPane prefHeight="318.0" prefWidth="523.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dev.wiing.gossip.client.controllers.MainChatController">
<children> <children>
<VBox fx:id="vboxVisitPage" alignment="CENTER" layoutX="10.0" layoutY="10.0" spacing="8.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> <VBox fx:id="vboxVisitPage" alignment="CENTER" layoutX="10.0" layoutY="10.0" spacing="8.0" visible="false" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children> <children>
@ -59,63 +58,6 @@
<ScrollPane fitToWidth="true" hbarPolicy="NEVER" VBox.vgrow="ALWAYS"> <ScrollPane fitToWidth="true" hbarPolicy="NEVER" VBox.vgrow="ALWAYS">
<content> <content>
<VBox fx:id="vboxMessages" spacing="16.0"> <VBox fx:id="vboxMessages" spacing="16.0">
<children>
<HBox spacing="8.0">
<children>
<Pane maxHeight="40.0" maxWidth="40.0" minHeight="40.0" minWidth="40.0" style="-fx-background-color: white;" />
<VBox alignment="CENTER_LEFT" minHeight="40.0" spacing="4.0" HBox.hgrow="ALWAYS">
<children>
<HBox alignment="BOTTOM_LEFT" spacing="8.0">
<children>
<Label style="-fx-font-size: 14;" text="\@ Username">
<styleClass>
<String fx:value="axis" />
<String fx:value="accent" />
</styleClass>
</Label>
<Label text="You">
<styleClass>
<String fx:value="tag" />
<String fx:value="small" />
<String fx:value="axis" />
</styleClass>
</Label>
<Label alignment="CENTER_RIGHT" contentDisplay="RIGHT" maxWidth="1.7976931348623157E308" style="-fx-font-size: 10;" styleClass="faint" text="10 minutes ago" textAlignment="RIGHT" HBox.hgrow="ALWAYS" />
</children>
</HBox>
<VBox spacing="4.0">
<children>
<Label text="Message contents" />
</children>
</VBox>
</children>
</VBox>
</children>
</HBox>
<HBox alignment="CENTER_LEFT" layoutX="26.0" layoutY="26.0" spacing="8.0">
<children>
<ImageView fitHeight="20.0" fitWidth="20.0" opacity="0.5" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/door-enter.png" />
</image>
</ImageView>
<Label style="-fx-font-size: 14;" styleClass="axis" text="\@ Username" />
<Label layoutX="46.0" layoutY="19.0" style="-fx-font-size: 12;" text="joined the Topic" />
<Label alignment="CENTER_RIGHT" contentDisplay="RIGHT" maxWidth="1.7976931348623157E308" style="-fx-font-size: 10;" styleClass="faint" text="10 minutes ago" textAlignment="RIGHT" HBox.hgrow="ALWAYS">
<HBox.margin>
<Insets />
</HBox.margin>
</Label>
</children>
<padding>
<Insets bottom="8.0" left="16.0" right="16.0" top="8.0" />
</padding>
<styleClass>
<String fx:value="elevated" />
<String fx:value="border-radius-small" />
</styleClass>
</HBox>
</children>
<padding> <padding>
<Insets bottom="16.0" left="16.0" right="16.0" top="16.0" /> <Insets bottom="16.0" left="16.0" right="16.0" top="16.0" />
</padding> </padding>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.String?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.HBox?>
<HBox alignment="CENTER_LEFT" spacing="8.0" stylesheets="@../styling.css" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="dev.wiing.gossip.client.controllers.item.SystemMessageItemController">
<children>
<ImageView fx:id="imgIcon" fitHeight="20.0" fitWidth="20.0" opacity="0.5" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../icons/door-enter.png" />
</image>
</ImageView>
<Label fx:id="lblPrefix" style="-fx-font-size: 12;" text="joined the Topic" />
<Label fx:id="lblContent" style="-fx-font-size: 14;" styleClass="axis" text="\@ Username" />
<Label fx:id="lblSuffix" layoutX="46.0" layoutY="19.0" style="-fx-font-size: 12;" text="joined the Topic" />
<Label fx:id="lblTimeAgo" alignment="CENTER_RIGHT" contentDisplay="RIGHT" maxWidth="1.7976931348623157E308" style="-fx-font-size: 10;" styleClass="faint" text="10 minutes ago" textAlignment="RIGHT" HBox.hgrow="ALWAYS">
<HBox.margin>
<Insets />
</HBox.margin>
</Label>
</children>
<padding>
<Insets bottom="8.0" left="16.0" right="16.0" top="8.0" />
</padding>
<styleClass>
<String fx:value="elevated" />
<String fx:value="border-radius-small" />
</styleClass>
</HBox>

View File

@ -1,9 +1,6 @@
package dev.wiing.gossip.lib; package dev.wiing.gossip.lib;
import dev.wiing.gossip.lib.packets.MessageCreatedPacket; import dev.wiing.gossip.lib.packets.*;
import dev.wiing.gossip.lib.packets.Packet;
import dev.wiing.gossip.lib.packets.TopicCreatedPacket;
import dev.wiing.gossip.lib.packets.TopicUpdatePacket;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -42,17 +39,14 @@ public class PacketHandler {
} }
public boolean runPacket(Packet packet) { public boolean runPacket(Packet packet) {
switch (packet.getType()) { return switch (packet.getType()) {
case TopicCreatedPacket.TYPE: case TopicCreatedPacket.TYPE -> runPacket(TopicCreatedPacket.class, (TopicCreatedPacket) packet);
return runPacket(TopicCreatedPacket.class, (TopicCreatedPacket)packet); case TopicUpdatePacket.TYPE -> runPacket(TopicUpdatePacket.class, (TopicUpdatePacket) packet);
case TopicListDataPacket.TYPE -> runPacket(TopicListDataPacket.class, (TopicListDataPacket) packet);
case TopicUpdatePacket.TYPE: case MessageCreatedPacket.TYPE -> runPacket(MessageCreatedPacket.class, (MessageCreatedPacket) packet);
return runPacket(TopicUpdatePacket.class, (TopicUpdatePacket)packet); case MessageSystemPacket.TYPE -> runPacket(MessageSystemPacket.class, (MessageSystemPacket) packet);
default -> false;
case MessageCreatedPacket.TYPE: };
return runPacket(MessageCreatedPacket.class, (MessageCreatedPacket)packet);
}
return false;
} }
public <T extends Packet> Set<PacketListener<T>> getListeners(Class<T> packetClass) { public <T extends Packet> Set<PacketListener<T>> getListeners(Class<T> packetClass) {
@ -69,7 +63,9 @@ public class PacketHandler {
{ {
add(TopicCreatedPacket.TYPE); add(TopicCreatedPacket.TYPE);
add(TopicUpdatePacket.TYPE); add(TopicUpdatePacket.TYPE);
add(TopicListDataPacket.TYPE);
add(MessageCreatedPacket.TYPE); add(MessageCreatedPacket.TYPE);
add(MessageSystemPacket.TYPE);
} }
}; };
} }

View File

@ -23,12 +23,21 @@ public class PacketManager {
add(new TopicCreatedPacket()); add(new TopicCreatedPacket());
add(new TopicJoinPacket()); add(new TopicJoinPacket());
add(new TopicUpdatePacket()); add(new TopicUpdatePacket());
add(new TopicListFetchPacket());
add(new TopicListDataPacket());
add(new TopicFetchPacket());
add(new TopicDataPacket());
add(new UserFetchPacket()); add(new UserFetchPacket());
add(new UserDataPacket()); add(new UserDataPacket());
add(new MessagePushPacket()); add(new MessagePushPacket());
add(new MessageCreatedPacket()); 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)); ByteBuffer buffer = ByteBuffer.wrap(stream.readNBytes(6));
if (buffer.capacity() < 6) return null; if (buffer.capacity() < 6) return null;
@ -64,10 +73,6 @@ public class PacketManager {
return packet.readBytes(ByteBuffer.wrap(stream.readNBytes(size)), size); 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 { public void writePacket(BufferedOutputStream stream, Packet packet) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(packet.getTotalLength()); ByteBuffer buffer = ByteBuffer.allocate(packet.getTotalLength());
@ -77,7 +82,6 @@ public class PacketManager {
packet.writeBytes(buffer); packet.writeBytes(buffer);
stream.write(buffer.array()); stream.write(buffer.array());
stream.flush();
} }
public void writePacket(OutputStream stream, Packet packet) throws IOException { public void writePacket(OutputStream stream, Packet packet) throws IOException {
@ -86,6 +90,14 @@ public class PacketManager {
outputStream.flush(); 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 { public void replyPacket(Packet source, Packet reply) throws IOException {
BufferedOutputStream outputStream = new BufferedOutputStream(source.getSource().getOutputStream()); BufferedOutputStream outputStream = new BufferedOutputStream(source.getSource().getOutputStream());
writePacket(outputStream, reply); writePacket(outputStream, reply);

View File

@ -3,34 +3,32 @@ package dev.wiing.gossip.lib.models;
import java.time.LocalDateTime; import java.time.LocalDateTime;
public class Message { public class Message {
private final User author; private long id;
private final Topic topic; private final Topic topic;
private final String contents;
private final LocalDateTime postTime; private final LocalDateTime postTime;
public Message(User author, Topic topic, String contents, LocalDateTime postTime) { public Message(long id, Topic topic, LocalDateTime postTime) {
this.author = author; this.id = id;
this.topic = topic; this.topic = topic;
this.contents = contents;
this.postTime = postTime; this.postTime = postTime;
} }
public Message(User author, Topic topic, String contents) { public Message(long id, Topic topic) {
this(author, topic, contents, LocalDateTime.now()); this(id, topic, LocalDateTime.now());
} }
public User getAuthor() { public long getId() {
return author; return id;
}
public void setId(long id) {
this.id = id;
} }
public Topic getTopic() { public Topic getTopic() {
return topic; return topic;
} }
public String getContents() {
return contents;
}
public LocalDateTime getPostTime() { public LocalDateTime getPostTime() {
return postTime; return postTime;
} }

View File

@ -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;
}
}

View File

@ -16,6 +16,9 @@ public class Topic {
private short color; private short color;
private final List<Message> messages = Collections.synchronizedList(new ArrayList<>()); private final List<Message> messages = Collections.synchronizedList(new ArrayList<>());
private final Map<Long, Message> messageByIDs = new ConcurrentHashMap<>();
private long messageTrackerID = 1;
public Topic(long id, String name, String description, User host, short color) { public Topic(long id, String name, String description, User host, short color) {
this.id = id; this.id = id;
@ -93,10 +96,19 @@ public class Topic {
public void addMessage(Message message) { public void addMessage(Message message) {
messages.add(message); messages.add(message);
messageByIDs.put(message.getId(), message);
this.changeSupport.firePropertyChange("messageAdd", null, 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) { public void addChangeListener(PropertyChangeListener listener) {
this.changeSupport.addPropertyChangeListener(listener); this.changeSupport.addPropertyChangeListener(listener);
} }

View File

@ -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;
}
}

View File

@ -7,8 +7,9 @@ import java.nio.ByteBuffer;
public class MessageCreatedPacket extends Packet { public class MessageCreatedPacket extends Packet {
public static final short TYPE = 0x32; 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 authorID;
private long topicID; private long topicID;
private final StringData contents = new StringData(); private final StringData contents = new StringData();
@ -17,6 +18,14 @@ public class MessageCreatedPacket extends Packet {
super(TYPE, LENGTH); super(TYPE, LENGTH);
} }
public long getMessageID() {
return messageID;
}
public void setMessageID(long messageID) {
this.messageID = messageID;
}
public long getAuthorID() { public long getAuthorID() {
return authorID; return authorID;
} }
@ -39,7 +48,7 @@ public class MessageCreatedPacket extends Packet {
public void setContents(String contents) { public void setContents(String contents) {
this.contents.setValue(contents); this.contents.setValue(contents);
setLength(16 + this.contents.getLength()); setLength(24 + this.contents.getLength());
} }
@Override @Override
@ -47,6 +56,7 @@ public class MessageCreatedPacket extends Packet {
MessageCreatedPacket packet = new MessageCreatedPacket(); MessageCreatedPacket packet = new MessageCreatedPacket();
packet.setLength(size); packet.setLength(size);
packet.messageID = buffer.getLong();
packet.authorID = buffer.getLong(); packet.authorID = buffer.getLong();
packet.topicID = buffer.getLong(); packet.topicID = buffer.getLong();
packet.contents.setBytes(buffer); packet.contents.setBytes(buffer);
@ -56,6 +66,7 @@ public class MessageCreatedPacket extends Packet {
@Override @Override
public void writeBytes(ByteBuffer buffer) { public void writeBytes(ByteBuffer buffer) {
buffer.putLong(messageID);
buffer.putLong(authorID); buffer.putLong(authorID);
buffer.putLong(topicID); buffer.putLong(topicID);
buffer.put(contents.getBytes()); buffer.put(contents.getBytes());

View File

@ -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());
}
}
}

View File

@ -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);
}
}

View File

@ -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<LongData> 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<LongData> 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());
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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<LongData> 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<LongData> 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());
}
}

View File

@ -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);
}
}

View File

@ -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<LongData> 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<LongData> 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());
}
}

View File

@ -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) {
}
}

View File

@ -134,4 +134,8 @@ public class Database {
logger.info("Topic removed: \"{}\" (#{})", topic.getName(), topic.getId()); logger.info("Topic removed: \"{}\" (#{})", topic.getName(), topic.getId());
} }
public List<Topic> getAllTopicsReadOnly() {
return Collections.unmodifiableList(topics.values().stream().toList());
}
} }

View File

@ -1,18 +1,15 @@
package dev.wiing.gossip.server; package dev.wiing.gossip.server;
import dev.wiing.gossip.lib.data.LongData; import dev.wiing.gossip.lib.data.LongData;
import dev.wiing.gossip.lib.models.Message; import dev.wiing.gossip.lib.models.*;
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.*; import dev.wiing.gossip.lib.packets.*;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.util.List;
public record UserSocket(Socket socket) implements Runnable { public record UserSocket(Socket socket) implements Runnable {
@ -33,7 +30,7 @@ public record UserSocket(Socket socket) implements Runnable {
if (!socket.isConnected()) if (!socket.isConnected())
break; break;
Packet packet = Globals.getPacketManager().readPacket(new BufferedInputStream(socket.getInputStream())); Packet packet = Globals.getPacketManager().readPacket(socket.getInputStream());
if (packet == null) continue; if (packet == null) continue;
@ -52,6 +49,14 @@ public record UserSocket(Socket socket) implements Runnable {
onUserJoinTopic((TopicJoinPacket) packet); onUserJoinTopic((TopicJoinPacket) packet);
break; break;
case TopicListFetchPacket.TYPE:
onTopicListFetch((TopicListFetchPacket) packet);
break;
case TopicFetchPacket.TYPE:
onFetchTopic((TopicFetchPacket) packet);
break;
case UserFetchPacket.TYPE: case UserFetchPacket.TYPE:
onFetchUser((UserFetchPacket) packet); onFetchUser((UserFetchPacket) packet);
break; break;
@ -59,6 +64,14 @@ public record UserSocket(Socket socket) implements Runnable {
case MessagePushPacket.TYPE: case MessagePushPacket.TYPE:
onUserMessage((MessagePushPacket) packet); onUserMessage((MessagePushPacket) packet);
break; break;
case MessageListFetchPacket.TYPE:
onMessageListFetch((MessageListFetchPacket) packet);
break;
case MessageFetchPacket.TYPE:
onFetchMessage((MessageFetchPacket) packet);
break;
} }
} catch (SocketException e) { } catch (SocketException e) {
loop = false; loop = false;
@ -120,10 +133,13 @@ public record UserSocket(Socket socket) implements Runnable {
if (requester == null) return; if (requester == null) return;
if (topic == null) return; if (topic == null) return;
if (!topic.hasUser(requester)) { if (topic.hasUser(requester)) return;
info("\"{}\" joined topic \"{}\" (#{})", topic.getHost().getUsername().toUpperCase(), topic.getName().toUpperCase(), topic.getId()); info("\"{}\" joined topic \"{}\" (#{})", topic.getHost().getUsername().toUpperCase(), topic.getName().toUpperCase(), topic.getId());
topic.addUser(requester); topic.addUser(requester);
}
SystemMessage message = new SystemMessage(topic.getNextMessageID(), topic, SystemMessage.SystemType.USER_JOIN, requester, "");
topic.addMessage(message);
AckPacket resp = new AckPacket(); AckPacket resp = new AckPacket();
resp.setAcknowledgement(packet); resp.setAcknowledgement(packet);
@ -138,17 +154,62 @@ public record UserSocket(Socket socket) implements Runnable {
updatePacket.setTopicID(topic.getId()); updatePacket.setTopicID(topic.getId());
updatePacket.getUsersJoined().add(new LongData().setValue(requester.getUserID())); 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()) { for (User user : Database.getInstance().getUsers()) {
Socket socket = Database.getInstance().getUserSocket(user.getUserID()); Socket socket = Database.getInstance().getUserSocket(user.getUserID());
try { try {
Globals.getPacketManager().writePacket(socket.getOutputStream(), updatePacket); Globals.getPacketManager().writeAllPackets(socket.getOutputStream(), updatePacket, systemPacket);
} catch (IOException e) { } catch (IOException e) {
logger.error(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) { private void onFetchUser(UserFetchPacket packet) {
User user = Database.getInstance().getUserByID(packet.getUserID()); User user = Database.getInstance().getUserByID(packet.getUserID());
@ -175,9 +236,9 @@ public record UserSocket(Socket socket) implements Runnable {
if (user == null) return; if (user == null) return;
if (topic == 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()); 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(); MessageCreatedPacket messageCreated = new MessageCreatedPacket();
messageCreated.setAuthorID(user.getUserID()); messageCreated.setAuthorID(user.getUserID());
messageCreated.setTopicID(topic.getId()); messageCreated.setTopicID(topic.getId());
messageCreated.setContents(message.getContents()); messageCreated.setContents(userMessage.getContents());
for (User babbler : topic.getUsersReadOnly().values()) { for (User babbler : topic.getUsersReadOnly().values()) {
Socket userSocket = Database.getInstance().getUserSocket(babbler.getUserID()); 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);
}
}
} }