mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-04-12 15:17:16 +03:00
216 lines
4.0 KiB
Go
216 lines
4.0 KiB
Go
package set
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// Returned when an added key already exists in the set.
|
|
var ErrCollision = errors.New("key already exists")
|
|
|
|
// Returned when a requested item does not exist in the set.
|
|
var ErrMissing = errors.New("item does not exist")
|
|
|
|
// ZeroValue can be used when we only care about the key, not about the value.
|
|
var ZeroValue = struct{}{}
|
|
|
|
// Interface is the Set interface
|
|
type Interface interface {
|
|
Clear() int
|
|
Each(fn IterFunc) error
|
|
// Add only if the item does not already exist
|
|
Add(item Item) error
|
|
// Set item, override if it already exists
|
|
Set(item Item) error
|
|
Get(key string) (Item, error)
|
|
In(key string) bool
|
|
Len() int
|
|
ListPrefix(prefix string) []Item
|
|
Remove(key string) error
|
|
Replace(oldKey string, item Item) error
|
|
}
|
|
|
|
type IterFunc func(key string, item Item) error
|
|
|
|
type Set struct {
|
|
sync.RWMutex
|
|
lookup map[string]Item
|
|
normalize func(string) string
|
|
}
|
|
|
|
// New creates a new set with case-insensitive keys
|
|
func New() *Set {
|
|
return &Set{
|
|
lookup: map[string]Item{},
|
|
normalize: normalize,
|
|
}
|
|
}
|
|
|
|
// Clear removes all items and returns the number removed.
|
|
func (s *Set) Clear() int {
|
|
s.Lock()
|
|
n := len(s.lookup)
|
|
s.lookup = map[string]Item{}
|
|
s.Unlock()
|
|
return n
|
|
}
|
|
|
|
// Len returns the size of the set right now.
|
|
func (s *Set) Len() int {
|
|
s.RLock()
|
|
defer s.RUnlock()
|
|
return len(s.lookup)
|
|
}
|
|
|
|
// In checks if an item exists in this set.
|
|
func (s *Set) In(key string) bool {
|
|
key = s.normalize(key)
|
|
s.RLock()
|
|
item, ok := s.lookup[key]
|
|
s.RUnlock()
|
|
if ok && item.Value() == nil {
|
|
s.cleanup(key)
|
|
ok = false
|
|
}
|
|
return ok
|
|
}
|
|
|
|
// Get returns an item with the given key.
|
|
func (s *Set) Get(key string) (Item, error) {
|
|
key = s.normalize(key)
|
|
s.RLock()
|
|
item, ok := s.lookup[key]
|
|
s.RUnlock()
|
|
|
|
if !ok {
|
|
return nil, ErrMissing
|
|
}
|
|
if item.Value() == nil {
|
|
s.cleanup(key)
|
|
}
|
|
|
|
return item, nil
|
|
}
|
|
|
|
// Remove potentially expired key (normalized).
|
|
func (s *Set) cleanup(key string) {
|
|
s.Lock()
|
|
item, ok := s.lookup[key]
|
|
if ok && item.Value() == nil {
|
|
delete(s.lookup, key)
|
|
}
|
|
s.Unlock()
|
|
}
|
|
|
|
// Add item to this set if it does not exist already.
|
|
func (s *Set) Add(item Item) error {
|
|
key := s.normalize(item.Key())
|
|
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
oldItem, found := s.lookup[key]
|
|
if found && oldItem.Value() != nil {
|
|
return ErrCollision
|
|
}
|
|
|
|
s.lookup[key] = item
|
|
return nil
|
|
}
|
|
|
|
// Set item to this set, even if it already exists.
|
|
func (s *Set) Set(item Item) error {
|
|
key := s.normalize(item.Key())
|
|
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
s.lookup[key] = item
|
|
return nil
|
|
}
|
|
|
|
// Remove item from this set.
|
|
func (s *Set) Remove(key string) error {
|
|
key = s.normalize(key)
|
|
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
_, found := s.lookup[key]
|
|
if !found {
|
|
return ErrMissing
|
|
}
|
|
delete(s.lookup, key)
|
|
return nil
|
|
}
|
|
|
|
// Replace oldKey with a new item, which might be a new key.
|
|
// Can be used to rename items.
|
|
func (s *Set) Replace(oldKey string, item Item) error {
|
|
newKey := s.normalize(item.Key())
|
|
oldKey = s.normalize(oldKey)
|
|
|
|
s.Lock()
|
|
defer s.Unlock()
|
|
|
|
if newKey != oldKey {
|
|
// Check if it already exists
|
|
_, found := s.lookup[newKey]
|
|
if found {
|
|
return ErrCollision
|
|
}
|
|
|
|
// Remove oldKey
|
|
_, found = s.lookup[oldKey]
|
|
if !found {
|
|
return ErrMissing
|
|
}
|
|
delete(s.lookup, oldKey)
|
|
}
|
|
|
|
// Add new item
|
|
s.lookup[newKey] = item
|
|
|
|
return nil
|
|
}
|
|
|
|
// Each loops over every item while holding a read lock and applies fn to each
|
|
// element.
|
|
func (s *Set) Each(fn IterFunc) error {
|
|
var err error
|
|
s.RLock()
|
|
for key, item := range s.lookup {
|
|
if item.Value() == nil {
|
|
// Expired
|
|
defer s.cleanup(key)
|
|
continue
|
|
}
|
|
if err = fn(key, item); err != nil {
|
|
// Abort early
|
|
break
|
|
}
|
|
}
|
|
s.RUnlock()
|
|
return err
|
|
}
|
|
|
|
// ListPrefix returns a list of items with a prefix, normalized.
|
|
// TODO: Add trie for efficient prefix lookup
|
|
func (s *Set) ListPrefix(prefix string) []Item {
|
|
r := []Item{}
|
|
prefix = s.normalize(prefix)
|
|
|
|
s.Each(func(key string, item Item) error {
|
|
if strings.HasPrefix(key, prefix) {
|
|
r = append(r, item)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
return r
|
|
}
|
|
|
|
func normalize(key string) string {
|
|
return strings.ToLower(key)
|
|
}
|