Merge pull request #3 from yunginnanet/cmap

This commit is contained in:
kayos 2022-03-20 16:39:14 -07:00 committed by GitHub
commit 12e6cfbe3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 108 deletions

@ -22,4 +22,4 @@ jobs:
run: go build -v ./... run: go build -v ./...
- name: Test - name: Test
run: go test -v ./... run: go test -race -v ./...

@ -5,6 +5,7 @@
package girc package girc
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -176,8 +177,8 @@ func handleJOIN(c *Client, e Event) {
defer c.state.notify(c, UPDATE_STATE) defer c.state.notify(c, UPDATE_STATE)
channel.addUser(user.Nick) channel.addUser(user.Nick, user)
user.addChannel(channel.Name) user.addChannel(channel.Name, channel)
// Assume extended-join (ircv3). // Assume extended-join (ircv3).
if len(e.Params) >= 2 { if len(e.Params) >= 2 {
@ -215,6 +216,12 @@ func handlePART(c *Client, e Event) {
return return
} }
c.state.Lock()
defer c.state.Unlock()
c.debug.Println("handlePart")
defer c.debug.Println("handlePart done for " + e.Params[0])
// TODO: does this work if it's not the bot? // TODO: does this work if it's not the bot?
// er yes, but needs a test case // er yes, but needs a test case
@ -226,6 +233,15 @@ func handlePART(c *Client, e Event) {
defer c.state.notify(c, UPDATE_STATE) defer c.state.notify(c, UPDATE_STATE)
if chn := c.LookupChannel(channel); chn != nil {
chn.UserList.Remove(e.Source.ID())
c.state.Unlock()
c.debug.Println(fmt.Sprintf("removed: %s, new count: %d", e.Source.ID(), chn.Len()))
c.state.Lock()
} else {
c.debug.Println("failed to lookup channel: " + channel)
}
if e.Source.ID() == c.GetID() { if e.Source.ID() == c.GetID() {
c.state.deleteChannel(channel) c.state.deleteChannel(channel)
return return
@ -592,8 +608,8 @@ func handleNAMES(c *Client, e Event) {
continue continue
} }
user.addChannel(channel.Name) user.addChannel(channel.Name, channel)
channel.addUser(s.ID()) channel.addUser(s.ID(), user)
// Don't append modes, overwrite them. // Don't append modes, overwrite them.
perms, _ := user.Perms.Lookup(channel.Name) perms, _ := user.Perms.Lookup(channel.Name)

@ -637,6 +637,9 @@ func (c *Client) UserList() []string {
users := make([]string, 0, len(c.state.users)) users := make([]string, 0, len(c.state.users))
for user := range c.state.users.IterBuffered() { for user := range c.state.users.IterBuffered() {
usr := user.Val.(*User) usr := user.Val.(*User)
if usr.Stale {
continue
}
users = append(users, usr.Nick) users = append(users, usr.Nick)
} }

176
state.go

@ -6,7 +6,6 @@ package girc
import ( import (
"fmt" "fmt"
"sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -106,7 +105,9 @@ type User struct {
// version of the channel list. // version of the channel list.
// //
// NOTE: If the ChannelList is empty for the user, then the user's info could be out of date. // NOTE: If the ChannelList is empty for the user, then the user's info could be out of date.
ChannelList []string `json:"channels"` // turns out Concurrent-Map implements json.Marhsal!
// https://github.com/orcaman/concurrent-map/blob/893feb299719d9cbb2cfbe08b6dd4eb567d8039d/concurrent_map.go#L305
ChannelList cmap.ConcurrentMap `json:"channels"`
// FirstSeen represents the first time that the user was seen by the // FirstSeen represents the first time that the user was seen by the
// client for the given channel. Only usable if from state, not in past. // client for the given channel. Only usable if from state, not in past.
@ -120,6 +121,8 @@ type User struct {
// channel. This supports non-rfc style modes like Admin, Owner, and HalfOp. // channel. This supports non-rfc style modes like Admin, Owner, and HalfOp.
Perms *UserPerms `json:"perms"` Perms *UserPerms `json:"perms"`
Stale bool
// Extras are things added on by additional tracking methods, which may // Extras are things added on by additional tracking methods, which may
// or may not work on the IRC server in mention. // or may not work on the IRC server in mention.
Extras struct { Extras struct {
@ -150,9 +153,15 @@ func (u User) Channels(c *Client) []*Channel {
var channels []*Channel var channels []*Channel
for i := 0; i < len(u.ChannelList); i++ { for listed := range u.ChannelList.IterBuffered() {
ch := c.state.lookupChannel(u.ChannelList[i]) chn, chok := listed.Val.(*Channel)
if chok {
channels = append(channels, chn)
continue
}
ch := c.state.lookupChannel(listed.Key)
if ch != nil { if ch != nil {
u.ChannelList.Set(listed.Key, ch)
channels = append(channels, ch) channels = append(channels, ch)
} }
} }
@ -177,13 +186,18 @@ func (u *User) Copy() *User {
} }
// addChannel adds the channel to the users channel list. // addChannel adds the channel to the users channel list.
func (u *User) addChannel(name string) { func (u *User) addChannel(name string, chn *Channel) {
name = ToRFC1459(name)
if u.InChannel(name) { if u.InChannel(name) {
return return
} }
u.ChannelList = append(u.ChannelList, ToRFC1459(name)) if u.ChannelList.Has(name) {
sort.Strings(u.ChannelList) return
}
u.ChannelList.Set(name, chn)
u.Perms.set(name, Perms{}) u.Perms.set(name, Perms{})
} }
@ -192,32 +206,17 @@ func (u *User) addChannel(name string) {
func (u *User) deleteChannel(name string) { func (u *User) deleteChannel(name string) {
name = ToRFC1459(name) name = ToRFC1459(name)
j := -1 u.ChannelList.Remove(name)
for i := 0; i < len(u.ChannelList); i++ {
if u.ChannelList[i] == name {
j = i
break
}
}
if j != -1 {
u.ChannelList = append(u.ChannelList[:j], u.ChannelList[j+1:]...)
}
u.Perms.remove(name) u.Perms.remove(name)
} }
// InChannel checks to see if a user is in the given channel. // InChannel checks to see if a user is in the given channel.
// Maybe don't rely on it though, hasn't been the same since the war. :^)
func (u *User) InChannel(name string) bool { func (u *User) InChannel(name string) bool {
name = ToRFC1459(name) name = ToRFC1459(name)
for i := 0; i < len(u.ChannelList); i++ { return u.ChannelList.Has(name)
if u.ChannelList[i] == name {
return true
}
}
return false
} }
// Lifetime represents the amount of time that has passed since we have first // Lifetime represents the amount of time that has passed since we have first
@ -248,8 +247,8 @@ type Channel struct {
// TODO: Figure out if these are all unix timestamps, if so, convert it to time.Time // TODO: Figure out if these are all unix timestamps, if so, convert it to time.Time
Created string `json:"created"` Created string `json:"created"`
// UserList is a sorted list of all users we are currently tracking within // UserList is a sorted list of all users we are currently tracking within
// the channel. Each is the nickname, and is rfc1459 compliant. // the channel. Each is the1 nickname, and is rfc1459 compliant.
UserList []string `json:"user_list"` UserList cmap.ConcurrentMap `json:"user_list"`
// Network is the name of the IRC network where this channel was found. // Network is the name of the IRC network where this channel was found.
// This has been added for the purposes of girc being used in multi-client scenarios with data persistence. // This has been added for the purposes of girc being used in multi-client scenarios with data persistence.
Network string `json:"network"` Network string `json:"network"`
@ -268,9 +267,10 @@ func (ch Channel) Users(c *Client) []*User {
var users []*User var users []*User
for i := 0; i < len(ch.UserList); i++ { for listed := range ch.UserList.IterBuffered() {
user := c.state.lookupUser(ch.UserList[i]) user := c.state.lookupUser(listed.Key)
if user != nil { if user != nil {
ch.UserList.Set(listed.Key, user)
users = append(users, user) users = append(users, user)
} }
} }
@ -287,8 +287,8 @@ func (ch Channel) Trusted(c *Client) []*User {
var users []*User var users []*User
for i := 0; i < len(ch.UserList); i++ { for listed := range ch.UserList.IterBuffered() {
user := c.state.lookupUser(ch.UserList[i]) user := c.state.lookupUser(listed.Key)
if user == nil { if user == nil {
continue continue
} }
@ -312,10 +312,16 @@ func (ch Channel) Admins(c *Client) []*User {
var users []*User var users []*User
for i := 0; i < len(ch.UserList); i++ { for listed := range ch.UserList.IterBuffered() {
user := c.state.lookupUser(ch.UserList[i]) ui := listed.Val
if user == nil { user, usrok := ui.(*User)
continue if !usrok {
user = c.state.lookupUser(listed.Key)
if user == nil {
continue
} else {
ch.UserList.Set(listed.Key, user)
}
} }
perms, ok := user.Perms.Lookup(ch.Name) perms, ok := user.Perms.Lookup(ch.Name)
@ -328,30 +334,17 @@ func (ch Channel) Admins(c *Client) []*User {
} }
// addUser adds a user to the users list. // addUser adds a user to the users list.
func (ch *Channel) addUser(nick string) { func (ch *Channel) addUser(nick string, usr *User) {
if ch.UserIn(nick) { if ch.UserIn(nick) {
return return
} }
ch.UserList.Set(ToRFC1459(nick), usr)
ch.UserList = append(ch.UserList, ToRFC1459(nick))
sort.Strings(ch.UserList)
} }
// deleteUser removes an existing user from the users list. // deleteUser removes an existing user from the users list.
func (ch *Channel) deleteUser(nick string) { func (ch *Channel) deleteUser(nick string) {
nick = ToRFC1459(nick) nick = ToRFC1459(nick)
ch.UserList.Remove(nick)
j := -1
for i := 0; i < len(ch.UserList); i++ {
if ch.UserList[i] == nick {
j = i
break
}
}
if j != -1 {
ch.UserList = append(ch.UserList[:j], ch.UserList[j+1:]...)
}
} }
// Copy returns a deep copy of a given channel. // Copy returns a deep copy of a given channel.
@ -373,20 +366,13 @@ func (ch *Channel) Copy() *Channel {
// Len returns the count of users in a given channel. // Len returns the count of users in a given channel.
func (ch *Channel) Len() int { func (ch *Channel) Len() int {
return len(ch.UserList) return ch.UserList.Count()
} }
// UserIn checks to see if a given user is in a channel. // UserIn checks to see if a given user is in a channel.
func (ch *Channel) UserIn(name string) bool { func (ch *Channel) UserIn(name string) bool {
name = ToRFC1459(name) name = ToRFC1459(name)
return ch.UserList.Has(name)
for i := 0; i < len(ch.UserList); i++ {
if ch.UserList[i] == name {
return true
}
}
return false
} }
// Lifetime represents the amount of time that has passed since we have first // Lifetime represents the amount of time that has passed since we have first
@ -407,7 +393,7 @@ func (s *state) createChannel(name string) (ok bool) {
s.channels.Set(ToRFC1459(name), &Channel{ s.channels.Set(ToRFC1459(name), &Channel{
Name: name, Name: name,
UserList: []string{}, UserList: cmap.New(),
Joined: time.Now(), Joined: time.Now(),
Network: s.client.NetworkName(), Network: s.client.NetworkName(),
Modes: NewCModes(supported, prefixes), Modes: NewCModes(supported, prefixes),
@ -427,10 +413,12 @@ func (s *state) deleteChannel(name string) {
chn := c.(*Channel) chn := c.(*Channel)
for _, user := range chn.UserList { for listed := range chn.UserList.IterBuffered() {
ui, _ := s.users.Get(user) ui, _ := s.users.Get(listed.Key)
usr := ui.(*User) usr, usrok := ui.(*User)
usr.deleteChannel(name) if usrok {
usr.deleteChannel(name)
}
} }
s.channels.Remove(name) s.channels.Remove(name)
@ -465,14 +453,15 @@ func (s *state) createUser(src *Source) (u *User, ok bool) {
} }
u = &User{ u = &User{
Nick: src.Name, Nick: src.Name,
Host: src.Host, Host: src.Host,
Ident: src.Ident, Ident: src.Ident,
Mask: src.Name + "!" + src.Ident + "@" + src.Host, Mask: src.Name + "!" + src.Ident + "@" + src.Host,
FirstSeen: time.Now(), ChannelList: cmap.New(),
LastActive: time.Now(), FirstSeen: time.Now(),
Network: s.client.NetworkName(), LastActive: time.Now(),
Perms: &UserPerms{channels: make(map[string]Perms)}, Network: s.client.NetworkName(),
Perms: &UserPerms{channels: make(map[string]Perms)},
} }
s.users.Set(src.ID(), u) s.users.Set(src.ID(), u)
@ -488,13 +477,11 @@ func (s *state) deleteUser(channelName, nick string) {
} }
if channelName == "" { if channelName == "" {
for i := 0; i < len(user.ChannelList); i++ { user.ChannelList.Clear()
ci, _ := s.channels.Get(user.ChannelList[i]) // While we do still want to remove them from the channels,
chn := ci.(*Channel) // We want to hold onto that user object regardless on if they dip-set.
chn.deleteUser(nick) // s.users.Remove(ToRFC1459(nick))
} user.Stale = true
s.users.Remove(ToRFC1459(nick))
return return
} }
@ -505,12 +492,8 @@ func (s *state) deleteUser(channelName, nick string) {
user.deleteChannel(channelName) user.deleteChannel(channelName)
channel.deleteUser(nick) channel.deleteUser(nick)
if user.ChannelList.Count() == 0 {
if len(user.ChannelList) == 0 { user.Stale = true
// This means they are no longer in any channels we track, delete
// them from state.
s.users.Remove(ToRFC1459(nick))
} }
} }
@ -524,11 +507,15 @@ func (s *state) renameUser(from, to string) {
} }
user := s.lookupUser(from) user := s.lookupUser(from)
if user == nil {
old, oldok := s.users.Pop(from)
if !oldok && user == nil {
return return
} }
s.users.Remove(from) if old != nil && user == nil {
user = old.(*User)
}
user.Nick = to user.Nick = to
user.LastActive = time.Now() user.LastActive = time.Now()
@ -536,13 +523,12 @@ func (s *state) renameUser(from, to string) {
for chanchan := range s.channels.IterBuffered() { for chanchan := range s.channels.IterBuffered() {
chi := chanchan.Val chi := chanchan.Val
chn := chi.(*Channel) chn, chok := chi.(*Channel)
for i := range chn.UserList { if !chok {
if chn.UserList[i] == from { continue
chn.UserList[i] = ToRFC1459(to) }
sort.Strings(chn.UserList) if old, oldok := chn.UserList.Pop(from); oldok {
break chn.UserList.Set(to, old)
}
} }
} }
} }

@ -194,8 +194,14 @@ func TestState(t *testing.T) {
return return
} }
if !reflect.DeepEqual(user.ChannelList, []string{"#channel", "#channel2"}) { if user.ChannelList.Count() != len([]string{"#channel", "#channel2"}) {
t.Errorf("User.ChannelList == %#v, wanted %#v", user.ChannelList, []string{"#channel", "#channel2"}) t.Errorf("user.ChannelList.Count() == %d, wanted %d",
user.ChannelList.Count(), len([]string{"#channel", "#channel2"}))
return
}
if !user.ChannelList.Has("#channel") || !user.ChannelList.Has("#channel2") {
t.Errorf("channel list is missing either #channel or #channel2")
return return
} }
@ -273,19 +279,30 @@ func TestState(t *testing.T) {
return return
} }
if !reflect.DeepEqual(user.ChannelList, []string{"#channel"}) { chi, chnok := user.ChannelList.Get("#channel")
t.Errorf("user.ChannelList == %q, wanted %q", user.ChannelList, []string{"#channel"}) chn, chiok := chi.(*Channel)
if !chnok || !chiok {
t.Errorf("should have been able to get a pointer by looking up #channel")
return return
} }
channel := c.LookupChannel("#channel") if chn == nil {
if channel == nil {
t.Error("Client.LookupChannel() returned nil for existing channel") t.Error("Client.LookupChannel() returned nil for existing channel")
return return
} }
if !reflect.DeepEqual(channel.UserList, []string{"notjones"}) { chi2, _ := user.ChannelList.Get("#channel2")
t.Errorf("channel.UserList == %q, wanted %q", channel.UserList, []string{"notjones"}) chn2, _ := chi2.(*Channel)
if chn2.Len() != len([]string{"notjones"}) {
t.Errorf("channel.UserList.Count() == %d, wanted %d",
chn2.Len(), len([]string{"notjones"}))
return
}
if !chn.UserList.Has("notjones") {
t.Errorf("missing notjones from channel.UserList")
return return
} }