package telegram import ( "sync" "github.com/zelenin/go-tdlib/client" log "github.com/sirupsen/logrus" ) const MUCOccupantsLimit int32 = 200 // MUCState holds MUC metadata type MUCState struct { Resources map[string]bool Occupants *MUCOccupantsLRU } // MUCOccupant represents a MUC occupant type MUCOccupant struct { Nickname string Affiliation string Role string Status client.ChatMemberStatus prev *MUCOccupant next *MUCOccupant id int64 } func (o *MUCOccupant) cutOut() (prev, next *MUCOccupant) { prev = o.prev next = o.next // -- * --- * -X- * -X- * --- * -- o.prev = nil o.next = nil if prev != nil { prev.next = next } if next != nil { next.prev = prev } return } func NewMUCState() *MUCState { return &MUCState{ Resources: make(map[string]bool), Occupants: NewMUCOccupantsLRU(), } } type MUCOccupantsLRU struct { m map[int64]*MUCOccupant rev map[string]int64 oldest *MUCOccupant newest *MUCOccupant lock sync.Mutex } func NewMUCOccupantsLRU() *MUCOccupantsLRU { return &MUCOccupantsLRU{ m: make(map[int64]*MUCOccupant), rev: make(map[string]int64), } } func (lru *MUCOccupantsLRU) Get(id int64) (*MUCOccupant, bool) { lru.lock.Lock() defer lru.lock.Unlock() occupant, ok := lru.m[id] return occupant, ok } func (lru *MUCOccupantsLRU) GetIdByNickname(nickname string) (int64, bool) { lru.lock.Lock() defer lru.lock.Unlock() id, ok := lru.rev[nickname] return id, ok } func (lru *MUCOccupantsLRU) cutOut(oldOccupant *MUCOccupant) (prev, next *MUCOccupant) { prev, next = oldOccupant.cutOut() if lru.oldest == oldOccupant { lru.oldest = next } if lru.newest == oldOccupant { lru.newest = prev } return } func (lru *MUCOccupantsLRU) insertNewest(occupant *MUCOccupant) { lru.newest.next = occupant occupant.prev = lru.newest occupant.next = nil lru.newest = occupant } // Set adds or replaces an occupant and possibly returns an occupant removed instead because of overflow func (lru *MUCOccupantsLRU) Set(id int64, occupant *MUCOccupant) (deleted *MUCOccupant) { lru.lock.Lock() defer lru.lock.Unlock() occupant.id = id oldOccupant, oldOk := lru.m[id] lru.m[id] = occupant if oldOk { lru.cutOut(oldOccupant) delete(lru.rev, oldOccupant.Nickname) lru.rev[occupant.Nickname] = id } if (lru.oldest == nil) != (lru.newest == nil) { log.Fatal("MRD MUDAQ") } if lru.oldest == nil && lru.newest == nil { lru.oldest = occupant lru.newest = occupant occupant.prev = nil occupant.next = nil } else { lru.insertNewest(occupant) } if len(lru.m) > int(MUCOccupantsLimit) && lru.oldest != nil { deleted = lru.oldest delete(lru.m, lru.oldest.id) delete(lru.rev, lru.oldest.Nickname) lru.cutOut(lru.oldest) } return } // Delete occupant by member ID func (lru *MUCOccupantsLRU) Delete(id int64) { lru.lock.Lock() defer lru.lock.Unlock() oldOccupant, oldOk := lru.m[id] delete(lru.m, id) if oldOk { lru.cutOut(oldOccupant) delete(lru.rev, oldOccupant.Nickname) } } // Bump raises the occupant in LRU func (lru *MUCOccupantsLRU) Bump(occupant *MUCOccupant) { lru.lock.Lock() defer lru.lock.Unlock() if lru.newest == occupant { // already at the top, nothing to do return } lru.cutOut(occupant) lru.insertNewest(occupant) } // Range loops over all occupants func (lru *MUCOccupantsLRU) Range() chan *MUCOccupant { lru.lock.Lock() occupantChan := make(chan *MUCOccupant, 1) go func() { defer func() { lru.lock.Unlock() close(occupantChan) }() for _, occupant := range lru.m { occupantChan <- occupant } }() return occupantChan } // Clear properly removes all occupants and their possible mutual references (not necessary in Golang, yet still) func (lru *MUCOccupantsLRU) Clear() { lru.lock.Lock() defer lru.lock.Unlock() for _, occupant := range lru.m { occupant.prev = nil occupant.next = nil } lru.m = make(map[int64]*MUCOccupant) lru.rev = make(map[string]int64) lru.oldest = nil lru.newest = nil }