Bump MUC occupants and send unavailable presences for ones removed from LRU

This commit is contained in:
Bohdan Horbeshko 2025-07-24 12:34:05 -04:00
parent f3eeba7273
commit f9be86b5db
2 changed files with 48 additions and 4 deletions

View file

@ -90,7 +90,8 @@ func (lru *MUCOccupantsLRU) insertNewest(occupant *MUCOccupant) {
lru.newest = occupant lru.newest = occupant
} }
func (lru *MUCOccupantsLRU) Set(key int64, occupant *MUCOccupant) { // Set adds or replaces an occupant and possibly returns an occupant removed instead because of overflow
func (lru *MUCOccupantsLRU) Set(key int64, occupant *MUCOccupant) (deleted *MUCOccupant) {
lru.lock.Lock() lru.lock.Lock()
defer lru.lock.Unlock() defer lru.lock.Unlock()
@ -117,11 +118,15 @@ func (lru *MUCOccupantsLRU) Set(key int64, occupant *MUCOccupant) {
} }
if len(lru.m) > int(MUCOccupantsLimit) && lru.oldest != nil { if len(lru.m) > int(MUCOccupantsLimit) && lru.oldest != nil {
deleted = lru.oldest
delete(lru.m, lru.oldest.key) delete(lru.m, lru.oldest.key)
lru.cutOut(lru.oldest) lru.cutOut(lru.oldest)
} }
return
} }
// Delete occupant by member ID
func (lru *MUCOccupantsLRU) Delete(key int64) { func (lru *MUCOccupantsLRU) Delete(key int64) {
lru.lock.Lock() lru.lock.Lock()
defer lru.lock.Unlock() defer lru.lock.Unlock()
@ -134,6 +139,7 @@ func (lru *MUCOccupantsLRU) Delete(key int64) {
} }
} }
// Bump raises the occupant in LRU
func (lru *MUCOccupantsLRU) Bump(occupant *MUCOccupant) { func (lru *MUCOccupantsLRU) Bump(occupant *MUCOccupant) {
lru.lock.Lock() lru.lock.Lock()
defer lru.lock.Unlock() defer lru.lock.Unlock()
@ -147,6 +153,7 @@ func (lru *MUCOccupantsLRU) Bump(occupant *MUCOccupant) {
lru.insertNewest(occupant) lru.insertNewest(occupant)
} }
// Range loops over all occupants
func (lru *MUCOccupantsLRU) Range() chan *MUCOccupant { func (lru *MUCOccupantsLRU) Range() chan *MUCOccupant {
lru.lock.Lock() lru.lock.Lock()
@ -166,6 +173,7 @@ func (lru *MUCOccupantsLRU) Range() chan *MUCOccupant {
return occupantChan return occupantChan
} }
// Clear properly removes all occupants and their possible mutual references (not necessary in Golang, yet still)
func (lru *MUCOccupantsLRU) Clear() { func (lru *MUCOccupantsLRU) Clear() {
lru.lock.Lock() lru.lock.Lock()
defer lru.lock.Unlock() defer lru.lock.Unlock()

View file

@ -567,6 +567,11 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
for mucId, state := range c.mucCache { for mucId, state := range c.mucCache {
occupant, ok := state.Occupants.Get(chatID) occupant, ok := state.Occupants.Get(chatID)
if ok { if ok {
if show == "" {
// Bump occupants who just went online
state.Occupants.Bump(occupant)
}
_, toJids := c.getMUCJoinedJIDs(mucId, state, false) _, toJids := c.getMUCJoinedJIDs(mucId, state, false)
newMucArgs := append( newMucArgs := append(
newArgs, newArgs,
@ -753,7 +758,8 @@ func (c *Client) mucCacheHasOccupant(mucID int64, memberID int64) bool {
return false // no MUC to be added to return false // no MUC to be added to
} }
_, ok = mucState.Occupants.Get(memberID) occupant, ok := mucState.Occupants.Get(memberID)
mucState.Occupants.Bump(occupant)
return ok return ok
} }
@ -780,12 +786,14 @@ func (c *Client) addMUCOccupant(mucID int64, memberID int64, affiliation, role s
) )
if err == nil { if err == nil {
mucState.Occupants.Set(memberID, &MUCOccupant{ deleted := mucState.Occupants.Set(memberID, &MUCOccupant{
Nickname: nickname, Nickname: nickname,
Affiliation: affiliation, Affiliation: affiliation,
Role: role, Role: role,
Status: status, Status: status,
}) })
c.kickStaleOccupant(mucID, deleted, mucState)
return true return true
} }
@ -832,12 +840,13 @@ func (c *Client) updateMUCsNickname(memberID int64, newNickname string) {
for mucId, state := range c.mucCache { for mucId, state := range c.mucCache {
oldOccupant, ok := state.Occupants.Get(memberID) oldOccupant, ok := state.Occupants.Get(memberID)
if ok { if ok {
state.Occupants.Set(memberID, &MUCOccupant{ deleted := state.Occupants.Set(memberID, &MUCOccupant{
Nickname: newNickname, Nickname: newNickname,
Affiliation: oldOccupant.Affiliation, Affiliation: oldOccupant.Affiliation,
Role: oldOccupant.Role, Role: oldOccupant.Role,
Status: oldOccupant.Status, Status: oldOccupant.Status,
}) })
c.kickStaleOccupant(mucId, deleted, state)
sMucId := gateway.MUCNODE(mucId) sMucId := gateway.MUCNODE(mucId)
unavailableStatusCodes := []uint16{303, 210} unavailableStatusCodes := []uint16{303, 210}
@ -925,6 +934,7 @@ func (c *Client) GetMyMUCNickname(chatID int64) (string, bool) {
if !ok { if !ok {
return "", false return "", false
} }
mucState.Occupants.Bump(occupant)
return occupant.Nickname, true return occupant.Nickname, true
} }
@ -2882,6 +2892,31 @@ func (c *Client) kickMeFromMUC(chatID int64, statusCodes []uint16, destroy bool,
return c.sendPresence(args...) return c.sendPresence(args...)
} }
// achtung: assuming a locked mucState context
func (c *Client) kickStaleOccupant(chatID int64, deleted *MUCOccupant, mucState *MUCState) {
if deleted == nil {
return
}
// u mad? put me back!
if c.me != nil && c.me.Id == deleted.key {
deleted = mucState.Occupants.Set(deleted.key, deleted)
if deleted == nil {
// WTF but okay
return
}
}
c.sendPresence(
gateway.SPFrom(gateway.MUCNODE(chatID)),
gateway.SPResource(deleted.Nickname),
gateway.SPType("unavailable"),
gateway.SPMUCAffiliation(deleted.Affiliation),
gateway.SPMUCRole(deleted.Role),
gateway.SPMUCStatusCodes([]uint16{307, 333}),
)
}
// MigrateToMUCs unsubscribes from legacy group chats and invites to MUCs // MigrateToMUCs unsubscribes from legacy group chats and invites to MUCs
func (c *Client) MigrateToMUCs() { func (c *Client) MigrateToMUCs() {
var chatIDs []int64 var chatIDs []int64
@ -3072,6 +3107,7 @@ func (c *Client) mucOccupantRolePresence(chatID, userID int64, status ChatMember
} else { } else {
occupant, ok := mucState.Occupants.Get(userID) occupant, ok := mucState.Occupants.Get(userID)
if ok { if ok {
mucState.Occupants.Bump(occupant)
occupant.Affiliation = newAffiliation occupant.Affiliation = newAffiliation
occupant.Role = newRole occupant.Role = newRole
} }