Typing indicator + Metrics

This commit is contained in:
2023-12-24 02:46:27 +02:00
parent ac58009119
commit a477a87c3d
20 changed files with 1201 additions and 316 deletions
@@ -4,9 +4,11 @@ import dev.wiing.gossip.lib.data.AuthSecret;
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.server.customs.TypingTopic;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.net.Socket;
import java.security.SecureRandom;
import java.util.*;
@@ -15,14 +17,14 @@ public class Database {
private static final Logger logger = LogManager.getLogger(Database.class);
private int userIdCounter = 1;
private int channelIDCounter = 1;
private final Map<Long, User> users = Collections.synchronizedMap(new HashMap<>());
private final Map<String, User> usersBySecret = Collections.synchronizedMap(new HashMap<>());
private final Map<Long, Socket> userSockets = Collections.synchronizedMap(new HashMap<>());
private final Set<String> usedUsernames = Collections.synchronizedSet(new HashSet<>());
private int topicIdCounter = 1;
private final Map<Long, Topic> topics = Collections.synchronizedMap(new HashMap<>());
private final Map<Socket, User> connectedUsers = Collections.synchronizedMap(new HashMap<>());
private final Map<Long, TypingTopic> topics = Collections.synchronizedMap(new HashMap<>());
private Database() {
@@ -41,7 +43,7 @@ public class Database {
random.nextBytes(secretBytes);
AuthSecret secret = new AuthSecret(secretBytes);
long userID = userIdCounter++;
long userID = channelIDCounter++;
User user = new User(username, iconID, userID);
@@ -49,6 +51,7 @@ public class Database {
userSockets.put(userID, socket);
usersBySecret.put(secret.getString(), user);
usedUsernames.add(username);
connectedUsers.put(socket, user);
logger.info("User created: \"{}\" (#{})", user.getUsername(), user.getUserID());
@@ -79,12 +82,7 @@ public class Database {
}
public User getUserOfSocket(Socket socket) {
return userSockets.entrySet().stream()
.filter(entry -> entry.getValue().equals(socket))
.findFirst()
.map(Map.Entry::getKey)
.map(this::getUserByID)
.orElse(null);
return connectedUsers.get(socket);
}
public void removeUser(User user) {
@@ -96,22 +94,43 @@ public class Database {
.findFirst()
.ifPresent(usersBySecret::remove);
userSockets.remove(user.getUserID());
usedUsernames.remove(user.getUsername());
disconnectUser(user);
logger.info("User removed: \"{}\" (#{})", user.getUsername(), user.getUserID());
}
public Topic createTopic(AuthSecret userSecret, String topicName, String topicDescription) {
public void disconnectUser(User user) {
Socket socket = null;
if (userSockets.containsKey(user.getUserID())) {
socket = userSockets.remove(user.getUserID());
if (socket != null && !socket.isClosed()) {
try {
socket.close();
} catch (IOException ignored) {
}
}
}
if (socket != null) {
connectedUsers.remove(socket);
logger.info("User disconnected: \"{}\" (#{})", user.getUsername(), user.getUserID());
}
}
public TypingTopic createTopic(AuthSecret userSecret, String topicName, String topicDescription) {
if (!usersBySecret.containsKey(userSecret.getString())) return null;
User user = usersBySecret.get(userSecret.getString());
short colorHue = (short)Math.abs((new Random().nextInt(360)));
Topic topic = new Topic(
topicIdCounter++,
TypingTopic topic = new TypingTopic(
channelIDCounter++,
topicName,
topicDescription,
user,
@@ -125,17 +144,17 @@ public class Database {
return topic;
}
public Topic getTopic(long topicID) {
public TypingTopic getTopic(long topicID) {
return topics.getOrDefault(topicID, null);
}
public void removeTopic(long topicID) {
Topic topic = topics.remove(topicID);
TypingTopic topic = topics.remove(topicID);
logger.info("Topic removed: \"{}\" (#{})", topic.getName(), topic.getId());
}
public List<Topic> getAllTopicsReadOnly() {
return Collections.unmodifiableList(topics.values().stream().toList());
public List<TypingTopic> getAllTopicsReadOnly() {
return topics.values().stream().toList();
}
}
@@ -3,13 +3,13 @@ package dev.wiing.gossip.server;
import dev.wiing.gossip.lib.data.LongData;
import dev.wiing.gossip.lib.models.*;
import dev.wiing.gossip.lib.packets.*;
import dev.wiing.gossip.server.customs.TypingTopic;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.util.List;
public record UserSocket(Socket socket) implements Runnable {
@@ -72,6 +72,10 @@ public record UserSocket(Socket socket) implements Runnable {
case MessageFetchPacket.TYPE:
onFetchMessage((MessageFetchPacket) packet);
break;
case TypingPingPacket.TYPE:
onTypingUser((TypingPingPacket) packet);
break;
}
} catch (SocketException e) {
loop = false;
@@ -82,7 +86,7 @@ public record UserSocket(Socket socket) implements Runnable {
// Connection lost
User user = Database.getInstance().getUserOfSocket(socket);
Database.getInstance().removeUser(user);
Database.getInstance().disconnectUser(user);
}
private void onRegisterUser(RegisterRequestPacket packet) {
@@ -118,6 +122,8 @@ public record UserSocket(Socket socket) implements Runnable {
for (User user : Database.getInstance().getUsers()) {
Socket socket = Database.getInstance().getUserSocket(user.getUserID());
if (socket == null) continue;
try {
Globals.getPacketManager().writePacket(socket.getOutputStream(), added);
} catch (IOException e) {
@@ -162,6 +168,8 @@ public record UserSocket(Socket socket) implements Runnable {
for (User user : Database.getInstance().getUsers()) {
Socket socket = Database.getInstance().getUserSocket(user.getUserID());
if (socket == null) continue;
try {
Globals.getPacketManager().writeAllPackets(socket.getOutputStream(), updatePacket, systemPacket);
} catch (IOException e) {
@@ -259,6 +267,8 @@ public record UserSocket(Socket socket) implements Runnable {
for (User babbler : topic.getUsersReadOnly().values()) {
Socket userSocket = Database.getInstance().getUserSocket(babbler.getUserID());
if (userSocket == null) continue;
try {
Globals.getPacketManager().writePacket(userSocket.getOutputStream(), messageCreated);
} catch (IOException e) {
@@ -306,6 +316,7 @@ public record UserSocket(Socket socket) implements Runnable {
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());
@@ -321,4 +332,68 @@ public record UserSocket(Socket socket) implements Runnable {
}
}
private void onTypingUser(TypingPingPacket packet) {
User user = Database.getInstance().getUserBySecret(packet.getAuth());
if (user == null) return;
TypingTopic topic = Database.getInstance().getTopic(packet.getTopicID());
if (topic == null || !topic.hasUser(user)) return;
if (packet.isTyping()) {
topic.addTypingUser(user);
if (topic.getTypingExpiry().containsKey(user)) topic.getTypingExpiry().get(user).interrupt();
Thread thread = new Thread(() -> {
try {
Thread.sleep(20_000);
} catch (InterruptedException e) {
return;
}
topic.removeTypingUser(user);
announceTypingList(topic);
topic.getTypingExpiry().remove(user);
});
thread.start();
topic.getTypingExpiry().put(user, thread);
} else {
topic.removeTypingUser(user);
Thread thread = topic.getTypingExpiry().remove(user);
if (thread != null && thread.isAlive()) thread.interrupt();
}
info("User #{} typing ping on #{}", user.getUserID(), topic.getId());
announceTypingList(topic);
}
private static void announceTypingList(TypingTopic topic) {
TypingListUpdatePacket resp = new TypingListUpdatePacket();
resp.setTopicID(topic.getId());
resp.getTypingMembers().addAll(topic.getTypingUsersReadOnly().stream()
.map(typing -> new LongData().setValue(typing.getUserID()))
.toList());
for (User babbler : topic.getUsersReadOnly().values()) {
Socket userSocket = Database.getInstance().getUserSocket(babbler.getUserID());
if (userSocket == null) continue;
try {
Globals.getPacketManager().writePacket(userSocket.getOutputStream(), resp);
} catch (IOException e) {
logger.error(e);
}
}
}
}
@@ -0,0 +1,22 @@
package dev.wiing.gossip.server.customs;
import dev.wiing.gossip.lib.models.Topic;
import dev.wiing.gossip.lib.models.User;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class TypingTopic extends Topic {
private final Map<User, Thread> typingExpiry = new ConcurrentHashMap<>();
public TypingTopic(long id, String name, String description, User host, short color) {
super(id, name, description, host, color);
}
public Map<User, Thread> getTypingExpiry() {
return typingExpiry;
}
}