Late-pulling

This commit is contained in:
2023-12-13 11:25:41 +02:00
parent 0b1270a916
commit fff9fb90fa
27 changed files with 1260 additions and 193 deletions
@@ -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<Short> 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<Long, User> userCache = new ConcurrentHashMap<>();
public User getUser(long userID) {
User result;
if ((result = userCache.getOrDefault(userID, null)) != null) {
return result;
}
private final DataCache<User> 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<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 null;
});
public Topic getTopic(long topicID) {
return topicCache.get(topicID);
}
}
@@ -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);
}
}
@@ -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);
@@ -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
@@ -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");
@@ -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<Message> messages = Collections.synchronizedList(new ArrayList<>());
private final List<UserMessage> 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<Message> getMessageReadOnly() {
return Collections.unmodifiableList(messages);
public List<UserMessage> getMessageReadOnly() {
return Collections.unmodifiableList(userMessages);
}
public static Pair<Parent, MessageItemController> createInstance() {
@@ -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");
}
}
@@ -12,11 +12,10 @@
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<?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>
<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>
@@ -59,63 +58,6 @@
<ScrollPane fitToWidth="true" hbarPolicy="NEVER" VBox.vgrow="ALWAYS">
<content>
<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>
<Insets bottom="16.0" left="16.0" right="16.0" top="16.0" />
</padding>
@@ -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>