package chat import ( "errors" "strings" "sync" ) var ErrIdTaken error = errors.New("id already taken") var ErrItemMissing error = errors.New("item does not exist") // Unique identifier for an item type Id string // A prefix for a unique identifier type IdPrefix Id // An interface for items to store-able in the set type Item interface { Id() Id } // Set with string lookup // TODO: Add trie for efficient prefix lookup? type Set struct { lookup map[Id]Item sync.RWMutex } // Create a new set func NewSet() *Set { return &Set{ lookup: map[Id]Item{}, } } // Remove all items and return the number removed func (s *Set) Clear() int { s.Lock() n := len(s.lookup) s.lookup = map[Id]Item{} s.Unlock() return n } // Size of the set right now func (s *Set) Len() int { return len(s.lookup) } // Check if user belongs in this set func (s *Set) In(item Item) bool { s.RLock() _, ok := s.lookup[item.Id()] s.RUnlock() return ok } // Get user by name func (s *Set) Get(id Id) (Item, error) { s.RLock() item, ok := s.lookup[id] s.RUnlock() if !ok { return nil, ErrItemMissing } return item, nil } // Add user to set if user does not exist already func (s *Set) Add(item Item) error { s.Lock() defer s.Unlock() _, found := s.lookup[item.Id()] if found { return ErrIdTaken } s.lookup[item.Id()] = item return nil } // Remove user from set func (s *Set) Remove(item Item) error { s.Lock() defer s.Unlock() id := item.Id() _, found := s.lookup[id] if !found { return ErrItemMissing } delete(s.lookup, id) return nil } // Loop over every item while holding a read lock and apply fn func (s *Set) Each(fn func(item Item)) { s.RLock() for _, item := range s.lookup { fn(item) } s.RUnlock() } // List users by prefix, case insensitive func (s *Set) ListPrefix(prefix string) []Item { r := []Item{} prefix = strings.ToLower(prefix) s.RLock() defer s.RUnlock() for id, item := range s.lookup { if !strings.HasPrefix(string(id), prefix) { continue } r = append(r, item) } return r }