diff --git a/go.mod b/go.mod index 1c85648b..a91e2a4b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ergochat/ergo -go 1.17 +go 1.18 require ( code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 diff --git a/irc/channel.go b/irc/channel.go index eaddd55b..f44de1e7 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -177,10 +177,7 @@ func (channel *Channel) ExportRegistration(includeFlags uint) (info RegisteredCh info.Bans = channel.lists[modes.BanMask].Masks() info.Invites = channel.lists[modes.InviteMask].Masks() info.Excepts = channel.lists[modes.ExceptMask].Masks() - info.AccountToUMode = make(map[string]modes.Mode) - for account, mode := range channel.accountToUMode { - info.AccountToUMode[account] = mode - } + info.AccountToUMode = utils.CopyMap(channel.accountToUMode) } if includeFlags&IncludeSettings != 0 { diff --git a/irc/channelmanager.go b/irc/channelmanager.go index 5d4f2cd7..a1921c4b 100644 --- a/irc/channelmanager.go +++ b/irc/channelmanager.go @@ -26,17 +26,17 @@ type ChannelManager struct { sync.RWMutex // tier 2 // chans is the main data structure, mapping casefolded name -> *Channel chans map[string]*channelManagerEntry - chansSkeletons utils.StringSet // skeletons of *unregistered* chans - registeredChannels utils.StringSet // casefolds of registered chans - registeredSkeletons utils.StringSet // skeletons of registered chans - purgedChannels utils.StringSet // casefolds of purged chans + chansSkeletons utils.HashSet[string] // skeletons of *unregistered* chans + registeredChannels utils.HashSet[string] // casefolds of registered chans + registeredSkeletons utils.HashSet[string] // skeletons of registered chans + purgedChannels utils.HashSet[string] // casefolds of purged chans server *Server } // NewChannelManager returns a new ChannelManager. func (cm *ChannelManager) Initialize(server *Server) { cm.chans = make(map[string]*channelManagerEntry) - cm.chansSkeletons = make(utils.StringSet) + cm.chansSkeletons = make(utils.HashSet[string]) cm.server = server // purging should work even if registration is disabled @@ -66,8 +66,8 @@ func (cm *ChannelManager) loadRegisteredChannels(config *Config) { cm.Lock() defer cm.Unlock() - cm.registeredChannels = make(utils.StringSet, len(rawNames)) - cm.registeredSkeletons = make(utils.StringSet, len(rawNames)) + cm.registeredChannels = make(utils.HashSet[string], len(rawNames)) + cm.registeredSkeletons = make(utils.HashSet[string], len(rawNames)) for _, name := range rawNames { cfname, err := CasefoldChannel(name) if err == nil { diff --git a/irc/channelreg.go b/irc/channelreg.go index 1a3a366e..bb0a851b 100644 --- a/irc/channelreg.go +++ b/irc/channelreg.go @@ -145,8 +145,8 @@ func (reg *ChannelRegistry) AllChannels() (result []string) { } // PurgedChannels returns the set of all casefolded channel names that have been purged -func (reg *ChannelRegistry) PurgedChannels() (result utils.StringSet) { - result = make(utils.StringSet) +func (reg *ChannelRegistry) PurgedChannels() (result utils.HashSet[string]) { + result = make(utils.HashSet[string]) prefix := fmt.Sprintf(keyChannelPurged, "") reg.server.store.View(func(tx *buntdb.Tx) error { diff --git a/irc/client.go b/irc/client.go index 6912483c..2e7fbc93 100644 --- a/irc/client.go +++ b/irc/client.go @@ -994,8 +994,8 @@ func (client *Client) ModeString() (str string) { } // Friends refers to clients that share a channel with this client. -func (client *Client) Friends(capabs ...caps.Capability) (result map[*Session]empty) { - result = make(map[*Session]empty) +func (client *Client) Friends(capabs ...caps.Capability) (result utils.HashSet[*Session]) { + result = make(utils.HashSet[*Session]) // look at the client's own sessions addFriendsToSet(result, client, capabs...) @@ -1010,19 +1010,19 @@ func (client *Client) Friends(capabs ...caps.Capability) (result map[*Session]em } // Friends refers to clients that share a channel or extended-monitor this client. -func (client *Client) FriendsMonitors(capabs ...caps.Capability) (result map[*Session]empty) { +func (client *Client) FriendsMonitors(capabs ...caps.Capability) (result utils.HashSet[*Session]) { result = client.Friends(capabs...) client.server.monitorManager.AddMonitors(result, client.nickCasefolded, capabs...) return } // helper for Friends -func addFriendsToSet(set map[*Session]empty, client *Client, capabs ...caps.Capability) { +func addFriendsToSet(set utils.HashSet[*Session], client *Client, capabs ...caps.Capability) { client.stateMutex.RLock() defer client.stateMutex.RUnlock() for _, session := range client.sessions { if session.capabilities.HasAll(capabs...) { - set[session] = empty{} + set.Add(session) } } } @@ -1575,7 +1575,7 @@ func (client *Client) addChannel(channel *Channel, simulated bool) (err error) { } else if client.oper == nil && len(client.channels) >= config.Channels.MaxChannelsPerClient { err = errTooManyChannels } else { - client.channels[channel] = empty{} // success + client.channels.Add(channel) // success } client.stateMutex.Unlock() diff --git a/irc/client_test.go b/irc/client_test.go index 7aeee37b..64d1036c 100644 --- a/irc/client_test.go +++ b/irc/client_test.go @@ -11,7 +11,7 @@ import ( func TestGenerateBatchID(t *testing.T) { var session Session - s := make(utils.StringSet) + s := make(utils.HashSet[string]) count := 100000 for i := 0; i < count; i++ { diff --git a/irc/config.go b/irc/config.go index 3c26577a..75fd1f27 100644 --- a/irc/config.go +++ b/irc/config.go @@ -704,8 +704,8 @@ type Config struct { // OperClass defines an assembled operator class. type OperClass struct { Title string - WhoisLine string `yaml:"whois-line"` - Capabilities utils.StringSet // map to make lookups much easier + WhoisLine string `yaml:"whois-line"` + Capabilities utils.HashSet[string] // map to make lookups much easier } // OperatorClasses returns a map of assembled operator classes from the given config. @@ -743,7 +743,7 @@ func (conf *Config) OperatorClasses() (map[string]*OperClass, error) { // create new operclass var oc OperClass - oc.Capabilities = make(utils.StringSet) + oc.Capabilities = make(utils.HashSet[string]) // get inhereted info from other operclasses if len(info.Extends) > 0 { diff --git a/irc/handlers.go b/irc/handlers.go index 65e459d5..e777acce 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -3424,7 +3424,7 @@ func whoHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response // Construct set of channels the client is in. userChannels := make(ChannelSet) for _, channel := range client.Channels() { - userChannels[channel] = empty{} + userChannels.Add(channel) } // Another client is a friend if they share at least one channel, or they are the same client. @@ -3437,7 +3437,7 @@ func whoHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response if channel.flags.HasMode(modes.Auditorium) { return false // TODO this should respect +v etc. } - if _, present := userChannels[channel]; present { + if userChannels.Has(channel) { return true } } diff --git a/irc/history/history.go b/irc/history/history.go index db3c0d22..28b2d891 100644 --- a/irc/history/history.go +++ b/irc/history/history.go @@ -203,7 +203,7 @@ func (list *Buffer) betweenHelper(start, end Selector, cutoff time.Time, pred Pr // returns all correspondents, in reverse time order func (list *Buffer) allCorrespondents() (results []TargetListing) { - seen := make(utils.StringSet) + seen := make(utils.HashSet[string]) list.RLock() defer list.RUnlock() diff --git a/irc/import.go b/irc/import.go index 27212e96..2ebf88dd 100644 --- a/irc/import.go +++ b/irc/import.go @@ -54,7 +54,7 @@ type databaseImport struct { Channels map[string]channelImport } -func serializeAmodes(raw map[string]string, validCfUsernames utils.StringSet) (result []byte, err error) { +func serializeAmodes(raw map[string]string, validCfUsernames utils.HashSet[string]) (result []byte, err error) { processed := make(map[string]int, len(raw)) for accountName, mode := range raw { if len(mode) != 1 { @@ -80,7 +80,7 @@ func doImportDBGeneric(config *Config, dbImport databaseImport, credsType Creden tx.Set(keySchemaVersion, strconv.Itoa(importDBSchemaVersion), nil) tx.Set(keyCloakSecret, utils.GenerateSecretKey(), nil) - cfUsernames := make(utils.StringSet) + cfUsernames := make(utils.HashSet[string]) skeletonToUsername := make(map[string]string) warnSkeletons := false diff --git a/irc/monitor.go b/irc/monitor.go index 65abe9ad..9131e81b 100644 --- a/irc/monitor.go +++ b/irc/monitor.go @@ -7,6 +7,7 @@ import ( "sync" "github.com/ergochat/ergo/irc/caps" + "github.com/ergochat/ergo/irc/utils" "github.com/ergochat/irc-go/ircmsg" ) @@ -17,21 +18,21 @@ type MonitorManager struct { // client -> (casefolded nick it's watching -> uncasefolded nick) watching map[*Session]map[string]string // casefolded nick -> clients watching it - watchedby map[string]map[*Session]empty + watchedby map[string]utils.HashSet[*Session] } func (mm *MonitorManager) Initialize() { mm.watching = make(map[*Session]map[string]string) - mm.watchedby = make(map[string]map[*Session]empty) + mm.watchedby = make(map[string]utils.HashSet[*Session]) } // AddMonitors adds clients using extended-monitor monitoring `client`'s nick to the passed user set. -func (manager *MonitorManager) AddMonitors(users map[*Session]empty, cfnick string, capabs ...caps.Capability) { +func (manager *MonitorManager) AddMonitors(users utils.HashSet[*Session], cfnick string, capabs ...caps.Capability) { manager.RLock() defer manager.RUnlock() for session := range manager.watchedby[cfnick] { if session.capabilities.Has(caps.ExtendedMonitor) && session.capabilities.HasAll(capabs...) { - users[session] = empty{} + users.Add(session) } } } @@ -70,7 +71,7 @@ func (manager *MonitorManager) Add(session *Session, nick string, limit int) err manager.watching[session] = make(map[string]string) } if manager.watchedby[cfnick] == nil { - manager.watchedby[cfnick] = make(map[*Session]empty) + manager.watchedby[cfnick] = make(utils.HashSet[*Session]) } if len(manager.watching[session]) >= limit { @@ -78,7 +79,7 @@ func (manager *MonitorManager) Add(session *Session, nick string, limit int) err } manager.watching[session][cfnick] = nick - manager.watchedby[cfnick][session] = empty{} + manager.watchedby[cfnick].Add(session) return nil } @@ -92,7 +93,7 @@ func (manager *MonitorManager) Remove(session *Session, nick string) (err error) manager.Lock() defer manager.Unlock() delete(manager.watching[session], cfnick) - delete(manager.watchedby[cfnick], session) + manager.watchedby[cfnick].Remove(session) return nil } @@ -102,7 +103,7 @@ func (manager *MonitorManager) RemoveAll(session *Session) { defer manager.Unlock() for cfnick := range manager.watching[session] { - delete(manager.watchedby[cfnick], session) + manager.watchedby[cfnick].Remove(session) } delete(manager.watching, session) } diff --git a/irc/nickname.go b/irc/nickname.go index 6e31cf4a..3fb36609 100644 --- a/irc/nickname.go +++ b/irc/nickname.go @@ -24,8 +24,8 @@ var ( "MemoServ", "BotServ", "OperServ", } - restrictedCasefoldedNicks = make(utils.StringSet) - restrictedSkeletons = make(utils.StringSet) + restrictedCasefoldedNicks = make(utils.HashSet[string]) + restrictedSkeletons = make(utils.HashSet[string]) ) func performNickChange(server *Server, client *Client, target *Client, session *Session, nickname string, rb *ResponseBuffer) error { diff --git a/irc/types.go b/irc/types.go index 9166befc..45da2536 100644 --- a/irc/types.go +++ b/irc/types.go @@ -9,28 +9,11 @@ import ( "time" "github.com/ergochat/ergo/irc/modes" + "github.com/ergochat/ergo/irc/utils" ) -type empty struct{} - // ClientSet is a set of clients. -type ClientSet map[*Client]empty - -// Add adds the given client to this set. -func (clients ClientSet) Add(client *Client) { - clients[client] = empty{} -} - -// Remove removes the given client from this set. -func (clients ClientSet) Remove(client *Client) { - delete(clients, client) -} - -// Has returns true if the given client is in this set. -func (clients ClientSet) Has(client *Client) bool { - _, ok := clients[client] - return ok -} +type ClientSet = utils.HashSet[*Client] type memberData struct { modes *modes.ModeSet @@ -60,4 +43,4 @@ func (members MemberSet) Has(member *Client) bool { } // ChannelSet is a set of channels. -type ChannelSet map[*Channel]empty +type ChannelSet = utils.HashSet[*Channel] diff --git a/irc/utils/types.go b/irc/utils/types.go index 249b486e..06b56649 100644 --- a/irc/utils/types.go +++ b/irc/utils/types.go @@ -5,13 +5,25 @@ package utils type empty struct{} -type StringSet map[string]empty +type HashSet[T comparable] map[T]empty -func (s StringSet) Has(str string) bool { - _, ok := s[str] +func (s HashSet[T]) Has(elem T) bool { + _, ok := s[elem] return ok } -func (s StringSet) Add(str string) { - s[str] = empty{} +func (s HashSet[T]) Add(elem T) { + s[elem] = empty{} +} + +func (s HashSet[T]) Remove(elem T) { + delete(s, elem) +} + +func CopyMap[K comparable, V any](input map[K]V) (result map[K]V) { + result = make(map[K]V, len(input)) + for key, value := range input { + result[key] = value + } + return } diff --git a/irc/znc.go b/irc/znc.go index dcc29155..a118e839 100644 --- a/irc/znc.go +++ b/irc/znc.go @@ -74,7 +74,7 @@ func timeToZncWireTime(t time.Time) (result string) { type zncPlaybackTimes struct { start time.Time end time.Time - targets utils.StringSet // nil for "*" (everything), otherwise the channel names + targets utils.HashSet[string] // nil for "*" (everything), otherwise the channel names setAt time.Time } @@ -134,7 +134,7 @@ func zncPlaybackPlayHandler(client *Client, command string, params []string, rb end = zncWireTimeToTime(params[3]) } - var targets utils.StringSet + var targets utils.HashSet[string] var nickTargets []string // three cases: @@ -157,7 +157,7 @@ func zncPlaybackPlayHandler(client *Client, command string, params []string, rb if params[1] == "*" { playPrivmsgs = true // XXX nil `targets` means "every channel" } else { - targets = make(utils.StringSet) + targets = make(utils.HashSet[string]) for _, targetName := range strings.Split(targetString, ",") { if strings.HasPrefix(targetName, "#") { if cfTarget, err := CasefoldChannel(targetName); err == nil {