Implemented storing user messages in collections, improved network organization.
This commit is contained in:
parent
cc5c90fd54
commit
fcfea0f70f
|
@ -11,12 +11,8 @@ import lombok.Getter;
|
|||
import lombok.Setter;
|
||||
import nl.andrewl.concord_client.gui.MainWindow;
|
||||
import nl.andrewl.concord_core.msg.Message;
|
||||
import nl.andrewl.concord_core.msg.MessageUtils;
|
||||
import nl.andrewl.concord_core.msg.Serializer;
|
||||
import nl.andrewl.concord_core.msg.types.Chat;
|
||||
import nl.andrewl.concord_core.msg.types.Identification;
|
||||
import nl.andrewl.concord_core.msg.types.ServerMetaData;
|
||||
import nl.andrewl.concord_core.msg.types.ServerWelcome;
|
||||
import nl.andrewl.concord_core.msg.types.*;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
|
@ -28,7 +24,6 @@ import java.util.Set;
|
|||
import java.util.UUID;
|
||||
|
||||
public class ConcordClient implements Runnable {
|
||||
|
||||
private final Socket socket;
|
||||
private final DataInputStream in;
|
||||
private final DataOutputStream out;
|
||||
|
@ -53,6 +48,10 @@ public class ConcordClient implements Runnable {
|
|||
this.id = welcome.getClientId();
|
||||
this.currentChannelId = welcome.getCurrentChannelId();
|
||||
this.serverMetaData = welcome.getMetaData();
|
||||
|
||||
// Start fetching initial data for the channel we were initially put into.
|
||||
this.sendMessage(new ChannelUsersRequest(this.currentChannelId));
|
||||
this.sendMessage(new ChatHistoryRequest(this.currentChannelId, ChatHistoryRequest.Source.CHANNEL, ""));
|
||||
} else {
|
||||
throw new IOException("Unexpected response from the server after sending identification message.");
|
||||
}
|
||||
|
|
|
@ -10,11 +10,17 @@ import nl.andrewl.concord_client.ConcordClient;
|
|||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* This panel occupies the center of the interface, and displays the list of
|
||||
* recent messages, along with an input text box for the user to type messages
|
||||
* into.
|
||||
*/
|
||||
public class ChannelChatBox extends Panel {
|
||||
private final ConcordClient client;
|
||||
private Border chatBorder;
|
||||
@Getter
|
||||
private final ChatList chatList;
|
||||
@Getter
|
||||
private final TextBox inputTextBox;
|
||||
public ChannelChatBox(ConcordClient client, Window window) {
|
||||
super(new BorderLayout());
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package nl.andrewl.concord_client.gui;
|
||||
|
||||
import com.googlecode.lanterna.gui2.Button;
|
||||
import com.googlecode.lanterna.gui2.Direction;
|
||||
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||
import com.googlecode.lanterna.gui2.Panel;
|
||||
import com.googlecode.lanterna.gui2.*;
|
||||
import nl.andrewl.concord_client.ConcordClient;
|
||||
import nl.andrewl.concord_core.msg.types.MoveToChannel;
|
||||
|
||||
|
@ -11,7 +8,6 @@ import java.io.IOException;
|
|||
|
||||
public class ChannelList extends Panel {
|
||||
private final ConcordClient client;
|
||||
|
||||
public ChannelList(ConcordClient client) {
|
||||
super(new LinearLayout(Direction.VERTICAL));
|
||||
this.client = client;
|
||||
|
@ -25,11 +21,12 @@ public class ChannelList extends Panel {
|
|||
name = "*" + name;
|
||||
}
|
||||
Button b = new Button(name, () -> {
|
||||
System.out.println("Sending request to go to channel " + channel.getName());
|
||||
try {
|
||||
client.sendMessage(new MoveToChannel(channel.getId()));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
if (!client.getCurrentChannelId().equals(channel.getId())) {
|
||||
try {
|
||||
client.sendMessage(new MoveToChannel(channel.getId()));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.addComponent(b, LinearLayout.createLayoutData(LinearLayout.Alignment.End));
|
||||
|
|
|
@ -5,8 +5,7 @@ import lombok.Getter;
|
|||
import nl.andrewl.concord_client.ClientMessageListener;
|
||||
import nl.andrewl.concord_client.ConcordClient;
|
||||
import nl.andrewl.concord_core.msg.Message;
|
||||
import nl.andrewl.concord_core.msg.types.Chat;
|
||||
import nl.andrewl.concord_core.msg.types.MoveToChannel;
|
||||
import nl.andrewl.concord_core.msg.types.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -24,16 +23,16 @@ public class ChatPanel extends Panel implements ClientMessageListener {
|
|||
private final UserList userList;
|
||||
|
||||
private final ConcordClient client;
|
||||
private final TextGUIThread guiThread;
|
||||
|
||||
public ChatPanel(ConcordClient client, Window window) {
|
||||
super(new BorderLayout());
|
||||
this.guiThread = window.getTextGUI().getGUIThread();
|
||||
this.client = client;
|
||||
this.channelChatBox = new ChannelChatBox(client, window);
|
||||
this.channelList = new ChannelList(client);
|
||||
this.channelList.setChannels();
|
||||
this.userList = new UserList();
|
||||
this.userList.addItem("andrew");
|
||||
this.userList.addItem("tester");
|
||||
this.userList = new UserList(client);
|
||||
|
||||
Border b;
|
||||
b = Borders.doubleLine("Channels");
|
||||
|
@ -53,9 +52,25 @@ public class ChatPanel extends Panel implements ClientMessageListener {
|
|||
this.channelChatBox.getChatList().addItem(chat);
|
||||
} else if (message instanceof MoveToChannel moveToChannel) {
|
||||
client.setCurrentChannelId(moveToChannel.getChannelId());
|
||||
this.channelList.setChannels();
|
||||
this.channelChatBox.getChatList().clearItems();
|
||||
this.channelChatBox.refreshBorder();
|
||||
try {
|
||||
client.sendMessage(new ChatHistoryRequest(moveToChannel.getChannelId(), ChatHistoryRequest.Source.CHANNEL, ""));
|
||||
client.sendMessage(new ChannelUsersRequest(moveToChannel.getChannelId()));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.guiThread.invokeLater(() -> {
|
||||
this.channelList.setChannels();
|
||||
this.channelChatBox.getChatList().clearItems();
|
||||
this.channelChatBox.refreshBorder();
|
||||
this.channelChatBox.getInputTextBox().takeFocus();
|
||||
});
|
||||
} else if (message instanceof ChannelUsersResponse channelUsersResponse) {
|
||||
this.guiThread.invokeLater(() -> {
|
||||
this.userList.updateUsers(channelUsersResponse);
|
||||
});
|
||||
} else if (message instanceof ChatHistoryResponse chatHistoryResponse) {
|
||||
System.out.println("Got chat history response: " + chatHistoryResponse.getSourceId());
|
||||
System.out.println(chatHistoryResponse.getMessages());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,27 @@
|
|||
package nl.andrewl.concord_client.gui;
|
||||
|
||||
import com.googlecode.lanterna.gui2.AbstractListBox;
|
||||
import com.googlecode.lanterna.gui2.Button;
|
||||
import com.googlecode.lanterna.gui2.Direction;
|
||||
import com.googlecode.lanterna.gui2.LinearLayout;
|
||||
import com.googlecode.lanterna.gui2.Panel;
|
||||
import nl.andrewl.concord_client.ConcordClient;
|
||||
import nl.andrewl.concord_core.msg.types.ChannelUsersResponse;
|
||||
|
||||
public class UserList extends AbstractListBox<String, UserList> {
|
||||
public class UserList extends Panel {
|
||||
private final ConcordClient client;
|
||||
|
||||
public UserList(ConcordClient client) {
|
||||
super(new LinearLayout(Direction.VERTICAL));
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public void updateUsers(ChannelUsersResponse usersResponse) {
|
||||
this.removeAllComponents();
|
||||
for (var user : usersResponse.getUsers()) {
|
||||
Button b = new Button(user.getName(), () -> {
|
||||
System.out.println("Opening DM channel with user " + user.getName() + ", id: " + user.getId());
|
||||
});
|
||||
this.addComponent(b);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ public class Serializer {
|
|||
registerType(3, MoveToChannel.class);
|
||||
registerType(4, ChatHistoryRequest.class);
|
||||
registerType(5, ChatHistoryResponse.class);
|
||||
registerType(6, ChannelUsersRequest.class);
|
||||
registerType(7, ChannelUsersResponse.class);
|
||||
}
|
||||
|
||||
private static void registerType(int id, Class<? extends Message> clazz) {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package nl.andrewl.concord_core.msg.types;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
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.UUID;
|
||||
|
||||
import static nl.andrewl.concord_core.msg.MessageUtils.*;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ChannelUsersRequest implements Message {
|
||||
private UUID channelId;
|
||||
|
||||
@Override
|
||||
public int getByteCount() {
|
||||
return UUID_BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(DataOutputStream o) throws IOException {
|
||||
writeUUID(this.channelId, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(DataInputStream i) throws IOException {
|
||||
this.channelId = readUUID(i);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package nl.andrewl.concord_core.msg.types;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
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;
|
||||
import java.util.UUID;
|
||||
|
||||
import static nl.andrewl.concord_core.msg.MessageUtils.*;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ChannelUsersResponse implements Message {
|
||||
private List<UserData> users;
|
||||
|
||||
@Override
|
||||
public int getByteCount() {
|
||||
return getByteSize(this.users);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(DataOutputStream o) throws IOException {
|
||||
writeList(this.users, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(DataInputStream i) throws IOException {
|
||||
try {
|
||||
this.users = readList(UserData.class, i);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class UserData implements Message {
|
||||
private UUID id;
|
||||
private String name;
|
||||
|
||||
@Override
|
||||
public int getByteCount() {
|
||||
return UUID_BYTES + getByteSize(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(DataOutputStream o) throws IOException {
|
||||
writeUUID(this.id, o);
|
||||
writeString(this.name, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(DataInputStream i) throws IOException {
|
||||
this.id = readUUID(i);
|
||||
this.name = readString(i);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package nl.andrewl.concord_core.msg.types;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import nl.andrewl.concord_core.msg.Message;
|
||||
|
@ -46,6 +47,7 @@ import static nl.andrewl.concord_core.msg.MessageUtils.*;
|
|||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ChatHistoryRequest implements Message {
|
||||
public enum Source {CHANNEL, THREAD, DIRECT_MESSAGE}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package nl.andrewl.concord_core.msg.types;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import nl.andrewl.concord_core.msg.Message;
|
||||
|
@ -17,6 +18,7 @@ import java.util.UUID;
|
|||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ChatHistoryResponse implements Message {
|
||||
private UUID sourceId;
|
||||
private ChatHistoryRequest.Source sourceType;
|
||||
|
|
|
@ -24,5 +24,24 @@
|
|||
<version>3.4.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.12.4</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>2.12.4</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>2.12.4</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
|
@ -1,9 +1,14 @@
|
|||
module concord_server {
|
||||
requires nitrite;
|
||||
requires static lombok;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires com.fasterxml.jackson.core;
|
||||
requires com.fasterxml.jackson.annotation;
|
||||
|
||||
requires java.base;
|
||||
requires java.logging;
|
||||
|
||||
requires concord_core;
|
||||
|
||||
opens nl.andrewl.concord_server.config to com.fasterxml.jackson.databind;
|
||||
}
|
|
@ -3,12 +3,12 @@ package nl.andrewl.concord_server;
|
|||
import lombok.Getter;
|
||||
import nl.andrewl.concord_core.msg.Message;
|
||||
import nl.andrewl.concord_core.msg.Serializer;
|
||||
import nl.andrewl.concord_core.msg.types.ChannelUsersResponse;
|
||||
import org.dizitart.no2.NitriteCollection;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
|
@ -23,19 +23,32 @@ public class Channel {
|
|||
|
||||
private final Set<ClientThread> connectedClients;
|
||||
|
||||
public Channel(ConcordServer server, UUID id, String name) {
|
||||
private final NitriteCollection messageCollection;
|
||||
|
||||
public Channel(ConcordServer server, UUID id, String name, NitriteCollection messageCollection) {
|
||||
this.server = server;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.connectedClients = ConcurrentHashMap.newKeySet();
|
||||
this.messageCollection = messageCollection;
|
||||
}
|
||||
|
||||
public void addClient(ClientThread clientThread) {
|
||||
this.connectedClients.add(clientThread);
|
||||
try {
|
||||
this.sendMessage(new ChannelUsersResponse(this.getUserData()));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void removeClient(ClientThread clientThread) {
|
||||
this.connectedClients.remove(clientThread);
|
||||
try {
|
||||
this.sendMessage(new ChannelUsersResponse(this.getUserData()));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,6 +66,15 @@ public class Channel {
|
|||
}
|
||||
}
|
||||
|
||||
public List<ChannelUsersResponse.UserData> getUserData() {
|
||||
List<ChannelUsersResponse.UserData> users = new ArrayList<>();
|
||||
for (var clientThread : this.getConnectedClients()) {
|
||||
users.add(new ChannelUsersResponse.UserData(clientThread.getClientId(), clientThread.getClientNickname()));
|
||||
}
|
||||
users.sort(Comparator.comparing(ChannelUsersResponse.UserData::getName));
|
||||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -2,7 +2,10 @@ package nl.andrewl.concord_server;
|
|||
|
||||
import nl.andrewl.concord_core.msg.types.MoveToChannel;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ChannelManager {
|
||||
|
@ -14,10 +17,6 @@ public class ChannelManager {
|
|||
this.server = server;
|
||||
this.channelNameMap = new ConcurrentHashMap<>();
|
||||
this.channelIdMap = new ConcurrentHashMap<>();
|
||||
Channel general = new Channel(server, server.getIdProvider().newId(), "general");
|
||||
Channel memes = new Channel(server, server.getIdProvider().newId(), "memes");
|
||||
this.addChannel(general);
|
||||
this.addChannel(memes);
|
||||
}
|
||||
|
||||
public Set<Channel> getChannels() {
|
||||
|
@ -44,7 +43,8 @@ public class ChannelManager {
|
|||
|
||||
public void moveToChannel(ClientThread client, Channel channel) {
|
||||
if (client.getCurrentChannel() != null) {
|
||||
client.getCurrentChannel().removeClient(client);
|
||||
var previousChannel = client.getCurrentChannel();
|
||||
previousChannel.removeClient(client);
|
||||
}
|
||||
channel.addClient(client);
|
||||
client.setCurrentChannel(channel);
|
||||
|
|
|
@ -6,7 +6,6 @@ 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.Identification;
|
||||
import nl.andrewl.concord_core.msg.types.ServerWelcome;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
|
@ -16,7 +15,7 @@ import java.util.UUID;
|
|||
|
||||
/**
|
||||
* This thread is responsible for handling the connection to a single client of
|
||||
* a server.
|
||||
* a server. The client thread acts as the server's representation of a client.
|
||||
*/
|
||||
@Log
|
||||
public class ClientThread extends Thread {
|
||||
|
@ -26,8 +25,11 @@ public class ClientThread extends Thread {
|
|||
|
||||
private final ConcordServer server;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private UUID clientId = null;
|
||||
@Getter
|
||||
@Setter
|
||||
private String clientNickname = null;
|
||||
|
||||
@Getter
|
||||
|
@ -43,7 +45,7 @@ public class ClientThread extends Thread {
|
|||
this.out = new DataOutputStream(socket.getOutputStream());
|
||||
}
|
||||
|
||||
public void sendToClient(Message message) {
|
||||
public synchronized void sendToClient(Message message) {
|
||||
try {
|
||||
Serializer.writeMessage(message, this.out);
|
||||
} catch (IOException e) {
|
||||
|
@ -51,7 +53,7 @@ public class ClientThread extends Thread {
|
|||
}
|
||||
}
|
||||
|
||||
public void sendToClient(byte[] bytes) {
|
||||
public synchronized void sendToClient(byte[] bytes) {
|
||||
try {
|
||||
this.out.write(bytes);
|
||||
this.out.flush();
|
||||
|
@ -112,8 +114,7 @@ public class ClientThread extends Thread {
|
|||
try {
|
||||
var msg = Serializer.readMessage(this.in);
|
||||
if (msg instanceof Identification id) {
|
||||
this.clientNickname = id.getNickname();
|
||||
this.clientId = this.server.registerClient(id, this);
|
||||
this.server.registerClient(id, this);
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -5,11 +5,15 @@ import lombok.extern.java.Log;
|
|||
import nl.andrewl.concord_core.msg.types.Identification;
|
||||
import nl.andrewl.concord_core.msg.types.ServerMetaData;
|
||||
import nl.andrewl.concord_core.msg.types.ServerWelcome;
|
||||
import nl.andrewl.concord_server.config.ServerConfig;
|
||||
import org.dizitart.no2.IndexOptions;
|
||||
import org.dizitart.no2.IndexType;
|
||||
import org.dizitart.no2.Nitrite;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
@ -22,6 +26,7 @@ import java.util.stream.Collectors;
|
|||
public class ConcordServer implements Runnable {
|
||||
private final Map<UUID, ClientThread> clients;
|
||||
private final int port;
|
||||
private final String name;
|
||||
@Getter
|
||||
private final IdProvider idProvider;
|
||||
@Getter
|
||||
|
@ -34,9 +39,11 @@ public class ConcordServer implements Runnable {
|
|||
@Getter
|
||||
private final ChannelManager channelManager;
|
||||
|
||||
public ConcordServer(int port) {
|
||||
this.port = port;
|
||||
public ConcordServer() {
|
||||
this.idProvider = new UUIDProvider();
|
||||
ServerConfig config = ServerConfig.loadOrCreate(Path.of("server-config.json"), idProvider);
|
||||
this.port = config.port();
|
||||
this.name = config.name();
|
||||
this.db = Nitrite.builder()
|
||||
.filePath("concord-server.db")
|
||||
.openOrCreate();
|
||||
|
@ -45,6 +52,33 @@ public class ConcordServer implements Runnable {
|
|||
this.executorService = Executors.newCachedThreadPool();
|
||||
this.eventManager = new EventManager(this);
|
||||
this.channelManager = new ChannelManager(this);
|
||||
for (var channelConfig : config.channels()) {
|
||||
this.channelManager.addChannel(new Channel(
|
||||
this,
|
||||
UUID.fromString(channelConfig.id()),
|
||||
channelConfig.name(),
|
||||
this.db.getCollection("channel-" + channelConfig.id())
|
||||
));
|
||||
}
|
||||
this.initDatabase();
|
||||
}
|
||||
|
||||
private void initDatabase() {
|
||||
for (var channel : this.channelManager.getChannels()) {
|
||||
var col = channel.getMessageCollection();
|
||||
if (!col.hasIndex("timestamp")) {
|
||||
log.info("Adding timestamp index to collection for channel " + channel.getName());
|
||||
col.createIndex("timestamp", IndexOptions.indexOptions(IndexType.NonUnique));
|
||||
}
|
||||
if (!col.hasIndex("senderNickname")) {
|
||||
log.info("Adding senderNickname index to collection for channel " + channel.getName());
|
||||
col.createIndex("senderNickname", IndexOptions.indexOptions(IndexType.Fulltext));
|
||||
}
|
||||
if (!col.hasIndex("message")) {
|
||||
log.info("Adding message index to collection for channel " + channel.getName());
|
||||
col.createIndex("message", IndexOptions.indexOptions(IndexType.Fulltext));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,28 +89,34 @@ public class ConcordServer implements Runnable {
|
|||
* channel.
|
||||
* @param identification The client's identification data.
|
||||
* @param clientThread The client manager thread.
|
||||
* @return The id of the client.
|
||||
*/
|
||||
public UUID registerClient(Identification identification, ClientThread clientThread) {
|
||||
public void registerClient(Identification identification, ClientThread clientThread) {
|
||||
var id = this.idProvider.newId();
|
||||
log.info("Registering new client " + identification.getNickname() + " with id " + id);
|
||||
this.clients.put(id, clientThread);
|
||||
clientThread.setClientId(id);
|
||||
clientThread.setClientNickname(identification.getNickname());
|
||||
// Send a welcome reply containing all the initial server info the client needs.
|
||||
ServerMetaData metaData = new ServerMetaData(
|
||||
"Testing Server",
|
||||
this.name,
|
||||
this.channelManager.getChannels().stream()
|
||||
.map(channel -> new ServerMetaData.ChannelData(channel.getId(), channel.getName()))
|
||||
.sorted(Comparator.comparing(ServerMetaData.ChannelData::getName))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
// Immediately add the client to the default channel and send the initial welcome message.
|
||||
var defaultChannel = this.channelManager.getChannelByName("general").orElseThrow();
|
||||
clientThread.sendToClient(new ServerWelcome(id, defaultChannel.getId(), metaData));
|
||||
// It is important that we send the welcome message first. The client expects this as the initial response to their identification message.
|
||||
defaultChannel.addClient(clientThread);
|
||||
clientThread.setCurrentChannel(defaultChannel);
|
||||
clientThread.sendToClient(new ServerWelcome(id, defaultChannel.getId(), metaData));
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* De-registers a client from the server, removing them from any channel
|
||||
* they're currently in.
|
||||
* @param clientId The id of the client to remove.
|
||||
*/
|
||||
public void deregisterClient(UUID clientId) {
|
||||
var client = this.clients.remove(clientId);
|
||||
if (client != null) {
|
||||
|
@ -104,7 +144,7 @@ public class ConcordServer implements Runnable {
|
|||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
var server = new ConcordServer(8123);
|
||||
var server = new ConcordServer();
|
||||
server.run();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,19 @@ package nl.andrewl.concord_server;
|
|||
|
||||
import lombok.extern.java.Log;
|
||||
import nl.andrewl.concord_core.msg.Message;
|
||||
import nl.andrewl.concord_core.msg.types.ChannelUsersRequest;
|
||||
import nl.andrewl.concord_core.msg.types.Chat;
|
||||
import nl.andrewl.concord_core.msg.types.ChatHistoryRequest;
|
||||
import nl.andrewl.concord_core.msg.types.MoveToChannel;
|
||||
import nl.andrewl.concord_server.event.ChannelMoveHandler;
|
||||
import nl.andrewl.concord_server.event.ChatHandler;
|
||||
import nl.andrewl.concord_server.event.MessageHandler;
|
||||
import nl.andrewl.concord_server.event.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The event manager is responsible for the server's ability to respond to
|
||||
* various client requests.
|
||||
*/
|
||||
@Log
|
||||
public class EventManager {
|
||||
private final Map<Class<? extends Message>, MessageHandler<?>> messageHandlers;
|
||||
|
@ -21,8 +25,25 @@ public class EventManager {
|
|||
this.messageHandlers = new HashMap<>();
|
||||
this.messageHandlers.put(Chat.class, new ChatHandler());
|
||||
this.messageHandlers.put(MoveToChannel.class, new ChannelMoveHandler());
|
||||
this.messageHandlers.put(ChannelUsersRequest.class, new ChannelUsersRequestHandler());
|
||||
this.messageHandlers.put(ChatHistoryRequest.class, new ChatHistoryRequestHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a new message that was sent from a client. Tries to find an
|
||||
* appropriate handler for the message, and if one is found, calls the
|
||||
* {@link MessageHandler#handle(Message, ClientThread, ConcordServer)}
|
||||
* method on it.
|
||||
* <p>
|
||||
* Note that it is expected that client threads will invoke this method
|
||||
* during their {@link ClientThread#run()} method, so concurrent
|
||||
* invocation is expected.
|
||||
* </p>
|
||||
* @param message The message that was sent by a client.
|
||||
* @param client The client thread that is used for communicating with the
|
||||
* client.
|
||||
* @param <T> The type of message.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Message> void handle(T message, ClientThread client) {
|
||||
MessageHandler<T> handler = (MessageHandler<T>) this.messageHandlers.get(message.getClass());
|
||||
|
@ -30,6 +51,7 @@ public class EventManager {
|
|||
try {
|
||||
handler.handle(message, client, this.server);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.warning("Exception occurred while handling message: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
package nl.andrewl.concord_server.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.java.Log;
|
||||
import nl.andrewl.concord_server.IdProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Log
|
||||
public record ServerConfig(
|
||||
String name,
|
||||
int port,
|
||||
ChannelConfig[] channels
|
||||
) {
|
||||
|
||||
public static record ChannelConfig (
|
||||
String id,
|
||||
String name,
|
||||
String description
|
||||
) {}
|
||||
|
||||
public static ServerConfig loadOrCreate(Path filePath, IdProvider idProvider) {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
ServerConfig config;
|
||||
if (Files.notExists(filePath)) {
|
||||
config = new ServerConfig(
|
||||
"My Concord Server",
|
||||
8123,
|
||||
new ServerConfig.ChannelConfig[]{
|
||||
new ServerConfig.ChannelConfig(idProvider.newId().toString(), "general", "Default channel for general discussion.")
|
||||
}
|
||||
);
|
||||
try (var out = Files.newOutputStream(filePath)) {
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(out, config);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
log.info(filePath + " does not exist. Creating it with initial values. Edit and restart to apply changes.");
|
||||
} else {
|
||||
try {
|
||||
config = mapper.readValue(Files.newInputStream(filePath), ServerConfig.class);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
log.info("Loaded configuration from " + filePath);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package nl.andrewl.concord_server.event;
|
||||
|
||||
import nl.andrewl.concord_core.msg.types.ChannelUsersRequest;
|
||||
import nl.andrewl.concord_core.msg.types.ChannelUsersResponse;
|
||||
import nl.andrewl.concord_server.ClientThread;
|
||||
import nl.andrewl.concord_server.ConcordServer;
|
||||
|
||||
public class ChannelUsersRequestHandler implements MessageHandler<ChannelUsersRequest> {
|
||||
@Override
|
||||
public void handle(ChannelUsersRequest msg, ClientThread client, ConcordServer server) throws Exception {
|
||||
var optionalChannel = server.getChannelManager().getChannelById(msg.getChannelId());
|
||||
if (optionalChannel.isPresent()) {
|
||||
var channel = optionalChannel.get();
|
||||
client.sendToClient(new ChannelUsersResponse(channel.getUserData()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,10 +12,8 @@ public class ChatHandler implements MessageHandler<Chat> {
|
|||
@Override
|
||||
public void handle(Chat msg, ClientThread client, ConcordServer server) throws IOException {
|
||||
server.getExecutorService().submit(() -> {
|
||||
var collection = server.getDb().getCollection("channel-" + client.getCurrentChannel().getId());
|
||||
var messageId = server.getIdProvider().newId();
|
||||
var collection = client.getCurrentChannel().getMessageCollection();
|
||||
Document doc = new Document(Map.of(
|
||||
"_id", messageId,
|
||||
"senderId", msg.getSenderId(),
|
||||
"senderNickname", msg.getSenderNickname(),
|
||||
"timestamp", msg.getTimestamp(),
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package nl.andrewl.concord_server.event;
|
||||
|
||||
import nl.andrewl.concord_core.msg.types.Chat;
|
||||
import nl.andrewl.concord_core.msg.types.ChatHistoryRequest;
|
||||
import nl.andrewl.concord_core.msg.types.ChatHistoryResponse;
|
||||
import nl.andrewl.concord_server.ClientThread;
|
||||
import nl.andrewl.concord_server.ConcordServer;
|
||||
import org.dizitart.no2.Document;
|
||||
import org.dizitart.no2.FindOptions;
|
||||
import org.dizitart.no2.SortOrder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ChatHistoryRequestHandler implements MessageHandler<ChatHistoryRequest> {
|
||||
@Override
|
||||
public void handle(ChatHistoryRequest msg, ClientThread client, ConcordServer server) throws Exception {
|
||||
var optionalChannel = server.getChannelManager().getChannelById(msg.getSourceId());
|
||||
if (optionalChannel.isPresent()) {
|
||||
var channel = optionalChannel.get();
|
||||
System.out.println("Looking for chats in channel-" + channel.getId());
|
||||
var col = server.getDb().getCollection("channel-" + channel.getId());
|
||||
var cursor = col.find(
|
||||
FindOptions.sort("timestamp", SortOrder.Ascending)
|
||||
.thenLimit(0, 10)
|
||||
);
|
||||
List<Chat> chats = new ArrayList<>(10);
|
||||
for (Document doc : cursor) {
|
||||
chats.add(new Chat(
|
||||
doc.get("senderId", UUID.class),
|
||||
doc.get("senderNickname", String.class),
|
||||
doc.get("timestamp", Long.class),
|
||||
doc.get("message", String.class)
|
||||
));
|
||||
}
|
||||
col.close();
|
||||
System.out.println(chats);
|
||||
client.sendToClient(new ChatHistoryResponse(msg.getSourceId(), msg.getSourceType(), chats));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue