diff --git a/core/src/main/groovy/com/muwire/core/chat/ChatAction.java b/core/src/main/groovy/com/muwire/core/chat/ChatAction.java new file mode 100644 index 00000000..1fa934b3 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/chat/ChatAction.java @@ -0,0 +1,5 @@ +package com.muwire.core.chat; + +enum ChatAction { + JOIN, LEAVE, SAY +} diff --git a/core/src/main/groovy/com/muwire/core/chat/ChatCommand.groovy b/core/src/main/groovy/com/muwire/core/chat/ChatCommand.groovy new file mode 100644 index 00000000..4c7681a4 --- /dev/null +++ b/core/src/main/groovy/com/muwire/core/chat/ChatCommand.groovy @@ -0,0 +1,18 @@ +package com.muwire.core.chat + +class ChatCommand { + private final ChatAction action + private final String payload + + ChatCommand(String source) { + int space = source.indexOf(' ') + if (space < 0) + throw new Exception("Invalid command $source") + String command = source.substring(0, space) + if (command.charAt(0) != '/') + throw new Exception("command doesn't start with / $source") + command = command.substring(1) + action = ChatAction.valueOf(command) + payload = source.substring(space + 1) + } +} diff --git a/core/src/main/groovy/com/muwire/core/chat/ChatServer.groovy b/core/src/main/groovy/com/muwire/core/chat/ChatServer.groovy index cdb53a88..0795834c 100644 --- a/core/src/main/groovy/com/muwire/core/chat/ChatServer.groovy +++ b/core/src/main/groovy/com/muwire/core/chat/ChatServer.groovy @@ -2,6 +2,7 @@ package com.muwire.core.chat import java.nio.charset.StandardCharsets import java.util.concurrent.ConcurrentHashMap +import java.util.logging.Level import com.muwire.core.Constants import com.muwire.core.EventBus @@ -11,21 +12,28 @@ import com.muwire.core.connection.Endpoint import com.muwire.core.trust.TrustService import com.muwire.core.util.DataUtil +import groovy.util.logging.Log import net.i2p.data.Base64 import net.i2p.data.Destination import net.i2p.util.ConcurrentHashSet +@Log class ChatServer { + public static final String CONSOLE = "__CONSOLE__" private final EventBus eventBus private final MuWireSettings settings private final TrustService trustService + private final Persona me private final Map connections = new ConcurrentHashMap() + private final Map> rooms = new ConcurrentHashMap<>() + private final Map> memberships = new ConcurrentHashMap<>() - ChatServer(EventBus eventBus, MuWireSettings settings, TrustService trustService) { + ChatServer(EventBus eventBus, MuWireSettings settings, TrustService trustService, Persona me) { this.eventBus = eventBus this.settings = settings this.trustService = trustService + this.me = me } public void handle(Endpoint endpoint) { @@ -71,7 +79,95 @@ class ChatServer { ChatConnection connection = new ChatConnection(eventBus, endpoint, client, true, trustService, settings) connections.put(endpoint.destination, connection) + joinRoom(client, CONSOLE) connection.start() eventBus.publish(new ChatConnectionEvent(connection : connection, status : ChatConnectionAttemptStatus.SUCCESSFUL, persona : client)) } + + private void joinRoom(Persona p, String room) { + Set existing = rooms.get(room) + if (existing == null) { + existing = new ConcurrentHashSet<>() + rooms.put(room, existing) + } + existing.add(p) + + Set membership = memberships.get(p) + if (membership == null) { + membership = new ConcurrentHashSet<>() + memberships.put(p, membership) + } + membership.add(room) + } + + private void leaveRoom(Persona p, String room) { + Set existing = rooms.get(room) + if (existing == null) { + log.warning(p.getHumanReadableName() + " leaving room they hadn't joined") + return + } + existing.remove(p) + if (existing.isEmpty()) + rooms.remove(room) + + Set membership = memberships.get(p) + if (membership == null) { + log.warning(p.getHumanReadableName() + " didn't have any memberships") + return + } + membership.remove(room) + if (membership.isEmpty()) + memberships.remove(p) + } + + void onChatMessageEvent(ChatMessageEvent e) { + if (e.host != me) + return + + ChatCommand command + try { + command = new ChatCommand(e.payload) + } catch (Exception badCommand) { + log.log(Level.WARNING, "bad chat command",badCommand) + return + } + + switch(command.action) { + case ChatAction.JOIN : processJoin(command.payload, e); break + case ChatAction.LEAVE : processLeave(e); break + case ChatAction.SAY : processSay(e); break + } + } + + private void processJoin(String room, ChatMessageEvent e) { + joinRoom(room, e.sender) + rooms[room].each { + if (it == e.sender) + return + connections[it.destination].sendChat(e) + } + } + + private void processLeave(ChatMessageEvent e) { + leaveRoom(e.room) + rooms.getOrDefault(e.room, []).each { + if (it == e.sender) + return + connections[it.destination].sendChat(e) + } + } + + private void processSay(ChatMessageEvent e) { + if (rooms.containsKey(e.room)) { + // not a private message + rooms[e.room].each { + if (it == e.sender) + return + connections[it.destination].sendChat(e) + } + } else { + Persona target = new Persona(new ByteArrayInputStream(Base64.decode(e.room))) + connections[target.destination]?.sendChat(e) + } + } }