From 3966c17dec253962165494cd85646f0e1ffca99c Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Sun, 15 Jun 2025 13:14:16 -0400 Subject: [PATCH] refactor update broadcast --- irc/channel.go | 15 +++++++++++++++ irc/handlers.go | 7 +++++-- irc/metadata.go | 46 ++++++++++++++++++++++------------------------ 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/irc/channel.go b/irc/channel.go index 1b449a70..9e052f37 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -7,6 +7,7 @@ package irc import ( "fmt" + "iter" "maps" "strconv" "strings" @@ -1676,6 +1677,20 @@ func (channel *Channel) auditoriumFriends(client *Client) (friends []*Client) { return } +func (channel *Channel) sessionsWithCap(capabs ...caps.Capability) iter.Seq[*Session] { + return func(yield func(*Session) bool) { + for _, member := range channel.Members() { + for _, sess := range member.Sessions() { + if sess.capabilities.HasAll(capabs...) { + if !yield(sess) { + return + } + } + } + } + } +} + // returns whether the client is visible to unprivileged users in the channel // (i.e., respecting auditorium mode). note that this assumes that the client // is a member; if the client is not, it may return true anyway diff --git a/irc/handlers.go b/irc/handlers.go index 6d6408a4..c2751a6f 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -3129,11 +3129,13 @@ func metadataHandler(server *Server, client *Client, msg ircmsg.Message, rb *Res targetChannel = server.channels.Get(target) if targetChannel != nil { targetObj = targetChannel + target = targetChannel.Name() // canonicalize case } } else { targetClient = server.clients.Get(target) if targetClient != nil { targetObj = targetClient + target = targetClient.Nick() // canonicalize case } } if targetObj == nil { @@ -3180,13 +3182,14 @@ func metadataHandler(server *Server, client *Client, msg ircmsg.Message, rb *Res server.logger.Debug("metadata", "setting", key, value, "on", target) targetObj.SetMetadata(key, value) - notifySubscribers(server, rb.session, target, key, value) + notifySubscribers(server, rb.session, targetObj, target, key, value) rb.Add(nil, server.name, RPL_KEYVALUE, client.Nick(), originalTarget, key, "*", value) } else { server.logger.Debug("metadata", "deleting", key, "on", target) + // TODO check success or failure here targetObj.DeleteMetadata(key) - notifySubscribers(server, rb.session, target, key, "") + notifySubscribers(server, rb.session, targetObj, target, key, "") rb.Add(nil, server.name, RPL_KEYNOTSET, client.Nick(), target, key, client.t("Key deleted")) } diff --git a/irc/metadata.go b/irc/metadata.go index 629f3878..1a061454 100644 --- a/irc/metadata.go +++ b/irc/metadata.go @@ -2,12 +2,13 @@ package irc import ( "errors" + "iter" + "maps" "regexp" "strings" "github.com/ergochat/ergo/irc/caps" "github.com/ergochat/ergo/irc/modes" - "github.com/ergochat/ergo/irc/utils" ) var ( @@ -24,34 +25,31 @@ type MetadataHaver = interface { CountMetadata() int } -func notifySubscribers(server *Server, session *Session, target string, key string, value string) { - var notify utils.HashSet[*Session] = make(utils.HashSet[*Session]) - targetChannel := server.channels.Get(target) - targetClient := server.clients.Get(target) +func notifySubscribers(server *Server, session *Session, targetObj MetadataHaver, targetName, key, value string) { + var recipientSessions iter.Seq[*Session] - if targetClient != nil { - notify = targetClient.FriendsMonitors(caps.Metadata) - // notify clients about changes regarding themselves - for _, s := range targetClient.Sessions() { - notify.Add(s) - } - } - if targetChannel != nil { - members := targetChannel.Members() - for _, m := range members { - for _, s := range m.Sessions() { - if s.capabilities.Has(caps.Metadata) { - notify.Add(s) - } - } + switch target := targetObj.(type) { + case *Client: + // TODO this case is expensive and might warrant rate-limiting + friends := target.FriendsMonitors(caps.Metadata) + // broadcast metadata update to other connected sessions + for _, s := range target.Sessions() { + friends.Add(s) } + recipientSessions = maps.Keys(friends) + case *Channel: + recipientSessions = target.sessionsWithCap(caps.Metadata) + default: + return // impossible } - // don't notify the session that made the change - notify.Remove(session) + broadcastMetadataUpdate(server, recipientSessions, session, targetName, key, value) +} - for s := range notify { - if !s.isSubscribedTo(key) { +func broadcastMetadataUpdate(server *Server, sessions iter.Seq[*Session], originator *Session, target, key, value string) { + for s := range sessions { + // don't notify the session that made the change + if s == originator || !s.isSubscribedTo(key) { continue }