diff --git a/core/src/main/java/nl/andrewl/concord_core/msg/Message.java b/core/src/main/java/nl/andrewl/concord_core/msg/Message.java index 9bd98b8..1d56223 100644 --- a/core/src/main/java/nl/andrewl/concord_core/msg/Message.java +++ b/core/src/main/java/nl/andrewl/concord_core/msg/Message.java @@ -82,4 +82,13 @@ public interface Message { if (read != length) throw new IOException("Not all bytes of a string of length " + length + " could be read."); return new String(data, StandardCharsets.UTF_8); } + + default void writeEnum(Enum value, DataOutputStream o) throws IOException { + o.writeInt(value.ordinal()); + } + + default > T readEnum(Class e, DataInputStream i) throws IOException { + int ordinal = i.readInt(); + return e.getEnumConstants()[ordinal]; + } } diff --git a/core/src/main/java/nl/andrewl/concord_core/msg/types/ChatHistoryRequest.java b/core/src/main/java/nl/andrewl/concord_core/msg/types/ChatHistoryRequest.java new file mode 100644 index 0000000..04e1a09 --- /dev/null +++ b/core/src/main/java/nl/andrewl/concord_core/msg/types/ChatHistoryRequest.java @@ -0,0 +1,71 @@ +package nl.andrewl.concord_core.msg.types; + +import lombok.Data; +import lombok.NoArgsConstructor; +import nl.andrewl.concord_core.msg.Message; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * A message which clients can send to the server to request some messages from + * the server's history of all sent messages from a particular source. Every + * request must provide the id of the source that messages should be fetched + * from, in addition to the type of source (channel, thread, dm). + *

+ * The query string is a specially-formatted string that allows you to + * filter results to only certain messages, using different parameters that + * are separated by the ; character. + *

+ *

+ * All query parameters are of the form param=value, where + * param is the case-sensitive name of the parameter, and + * value is the value of the parameter. + *

+ *

+ * The following query parameters are supported: + *

+ *

+ *

+ * Responses to this request are sent via {@link ChatHistoryResponse}, where + * the list of messages is always sorted by the timestamp. + *

+ */ +@Data +@NoArgsConstructor +public class ChatHistoryRequest implements Message { + public enum Source {CHANNEL, THREAD, DIRECT_MESSAGE} + + private long sourceId; + private Source sourceType; + private String query; + + @Override + public int getByteCount() { + return Long.BYTES + Integer.BYTES + getByteSize(this.query); + } + + @Override + public void write(DataOutputStream o) throws IOException { + o.writeLong(sourceId); + writeEnum(this.sourceType, o); + writeString(this.query, o); + } + + @Override + public void read(DataInputStream i) throws IOException { + this.sourceId = i.readLong(); + this.sourceType = readEnum(Source.class, i); + this.query = readString(i); + } +} diff --git a/core/src/main/java/nl/andrewl/concord_core/msg/types/ChatHistoryResponse.java b/core/src/main/java/nl/andrewl/concord_core/msg/types/ChatHistoryResponse.java new file mode 100644 index 0000000..84aa044 --- /dev/null +++ b/core/src/main/java/nl/andrewl/concord_core/msg/types/ChatHistoryResponse.java @@ -0,0 +1,54 @@ +package nl.andrewl.concord_core.msg.types; + +import lombok.Data; +import lombok.NoArgsConstructor; +import nl.andrewl.concord_core.msg.Message; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.List; + +/** + * The response that a server sends to a {@link ChatHistoryRequest}. + */ +@Data +@NoArgsConstructor +public class ChatHistoryResponse implements Message { + private long sourceId; + private ChatHistoryRequest.Source sourceType; + List messages; + + @Override + public int getByteCount() { + int count = Long.BYTES + Integer.BYTES + Integer.BYTES; + for (var message : this.messages) { + count += message.getByteCount(); + } + return count; + } + + @Override + public void write(DataOutputStream o) throws IOException { + o.writeLong(this.sourceId); + writeEnum(this.sourceType, o); + o.writeInt(messages.size()); + for (var message : this.messages) { + message.write(o); + } + } + + @Override + public void read(DataInputStream i) throws IOException { + this.sourceId = i.readInt(); + this.sourceType = readEnum(ChatHistoryRequest.Source.class, i); + int messageCount = i.readInt(); + Chat[] messages = new Chat[messageCount]; + for (int k = 0; k < messageCount; k++) { + Chat c = new Chat(); + c.read(i); + messages[k] = c; + } + this.messages = List.of(messages); + } +} diff --git a/server/src/main/java/nl/andrewl/concord_server/ClientThread.java b/server/src/main/java/nl/andrewl/concord_server/ClientThread.java index 6fccd3b..d4d99f5 100644 --- a/server/src/main/java/nl/andrewl/concord_server/ClientThread.java +++ b/server/src/main/java/nl/andrewl/concord_server/ClientThread.java @@ -5,6 +5,7 @@ import lombok.extern.java.Log; import nl.andrewl.concord_core.msg.Message; import nl.andrewl.concord_core.msg.Serializer; import nl.andrewl.concord_core.msg.types.Chat; +import nl.andrewl.concord_core.msg.types.ChatHistoryRequest; import nl.andrewl.concord_core.msg.types.Identification; import nl.andrewl.concord_core.msg.types.ServerWelcome; @@ -65,6 +66,8 @@ public class ClientThread extends Thread { var msg = Serializer.readMessage(this.in); if (msg instanceof Chat chat) { this.server.handleChat(chat); + } else if (msg instanceof ChatHistoryRequest historyRequest) { + this.server.handleHistoryRequest(historyRequest, this); } } catch (IOException e) { log.info("Client disconnected: " + e.getMessage()); diff --git a/server/src/main/java/nl/andrewl/concord_server/ConcordServer.java b/server/src/main/java/nl/andrewl/concord_server/ConcordServer.java index b60b329..e10936a 100644 --- a/server/src/main/java/nl/andrewl/concord_server/ConcordServer.java +++ b/server/src/main/java/nl/andrewl/concord_server/ConcordServer.java @@ -3,6 +3,9 @@ package nl.andrewl.concord_server; import lombok.extern.java.Log; import nl.andrewl.concord_core.msg.Serializer; import nl.andrewl.concord_core.msg.types.Chat; +import nl.andrewl.concord_core.msg.types.ChatHistoryRequest; +import org.dizitart.no2.Document; +import org.dizitart.no2.Nitrite; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; @@ -19,10 +22,14 @@ public class ConcordServer implements Runnable { private final Map clients = new ConcurrentHashMap<>(32); private final int port; private final Random random; + private final Nitrite db; public ConcordServer(int port) { this.port = port; this.random = new SecureRandom(); + this.db = Nitrite.builder() + .filePath("concord-server.db") + .openOrCreate(); } public long registerClient(ClientThread clientThread) { @@ -37,6 +44,15 @@ public class ConcordServer implements Runnable { } public void handleChat(Chat chat) { + var collection = db.getCollection("channel-TEST"); + long messageId = this.random.nextLong(); + Document doc = Document.createDocument(Long.toHexString(messageId), "message") + .put("senderId", Long.toHexString(chat.getSenderId())) + .put("senderNickname", chat.getSenderNickname()) + .put("timestamp", chat.getTimestamp()) + .put("message", chat.getMessage()); + collection.insert(doc); + db.commit(); System.out.println(chat.getSenderNickname() + ": " + chat.getMessage()); ByteArrayOutputStream baos = new ByteArrayOutputStream(chat.getByteCount()); try { @@ -51,6 +67,10 @@ public class ConcordServer implements Runnable { } } + public void handleHistoryRequest(ChatHistoryRequest request, ClientThread clientThread) { + + } + @Override public void run() { ServerSocket serverSocket;