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 ./...
- name: Test
run: go test -v ./...
run: go test -race -v ./...

@ -5,6 +5,7 @@
package girc
import (
"fmt"
"strconv"
"strings"
"time"
@ -176,8 +177,8 @@ func handleJOIN(c *Client, e Event) {
defer c.state.notify(c, UPDATE_STATE)
channel.addUser(user.Nick)
user.addChannel(channel.Name)
channel.addUser(user.Nick, user)
user.addChannel(channel.Name, channel)
// Assume extended-join (ircv3).
if len(e.Params) >= 2 {
@ -215,6 +216,12 @@ func handlePART(c *Client, e Event) {
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?
// er yes, but needs a test case
@ -226,6 +233,15 @@ func handlePART(c *Client, e Event) {
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() {
c.state.deleteChannel(channel)
return
@ -592,8 +608,8 @@ func handleNAMES(c *Client, e Event) {
continue
}
user.addChannel(channel.Name)
channel.addUser(s.ID())
user.addChannel(channel.Name, channel)
channel.addUser(s.ID(), user)
// Don't append modes, overwrite them.
perms, _ := user.Perms.Lookup(channel.Name)

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

176
state.go

@ -6,7 +6,6 @@ package girc
import (
"fmt"
"sort"
"sync"
"sync/atomic"
"time"
@ -106,7 +105,9 @@ type User struct {
// version of the channel list.
//
// 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
// 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.
Perms *UserPerms `json:"perms"`
Stale bool
// Extras are things added on by additional tracking methods, which may
// or may not work on the IRC server in mention.
Extras struct {
@ -150,9 +153,15 @@ func (u User) Channels(c *Client) []*Channel {
var channels []*Channel
for i := 0; i < len(u.ChannelList); i++ {
ch := c.state.lookupChannel(u.ChannelList[i])
for listed := range u.ChannelList.IterBuffered() {
chn, chok := listed.Val.(*Channel)
if chok {
channels = append(channels, chn)
continue
}
ch := c.state.lookupChannel(listed.Key)
if ch != nil {
u.ChannelList.Set(listed.Key, ch)
channels = append(channels, ch)
}
}
@ -177,13 +186,18 @@ func (u *User) Copy() *User {
}
// 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) {
return
}
u.ChannelList = append(u.ChannelList, ToRFC1459(name))
sort.Strings(u.ChannelList)
if u.ChannelList.Has(name) {
return
}
u.ChannelList.Set(name, chn)
u.Perms.set(name, Perms{})
}
@ -192,32 +206,17 @@ func (u *User) addChannel(name string) {
func (u *User) deleteChannel(name string) {
name = ToRFC1459(name)
j := -1
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.ChannelList.Remove(name)
u.Perms.remove(name)
}
// 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 {
name = ToRFC1459(name)
for i := 0; i < len(u.ChannelList); i++ {
if u.ChannelList[i] == name {
return true
}
}
return false
return u.ChannelList.Has(name)
}
// 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
Created string `json:"created"`
// UserList is a sorted list of all users we are currently tracking within
// the channel. Each is the nickname, and is rfc1459 compliant.
UserList []string `json:"user_list"`
// the channel. Each is the1 nickname, and is rfc1459 compliant.
UserList cmap.ConcurrentMap `json:"user_list"`
// 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.
Network string `json:"network"`
@ -268,9 +267,10 @@ func (ch Channel) Users(c *Client) []*User {
var users []*User
for i := 0; i < len(ch.UserList); i++ {
user := c.state.lookupUser(ch.UserList[i])
for listed := range ch.UserList.IterBuffered() {
user := c.state.lookupUser(listed.Key)
if user != nil {
ch.UserList.Set(listed.Key, user)
users = append(users, user)
}
}
@ -287,8 +287,8 @@ func (ch Channel) Trusted(c *Client) []*User {
var users []*User
for i := 0; i < len(ch.UserList); i++ {
user := c.state.lookupUser(ch.UserList[i])
for listed := range ch.UserList.IterBuffered() {
user := c.state.lookupUser(listed.Key)
if user == nil {
continue
}
@ -312,10 +312,16 @@ func (ch Channel) Admins(c *Client) []*User {
var users []*User
for i := 0; i < len(ch.UserList); i++ {
user := c.state.lookupUser(ch.UserList[i])
if user == nil {
continue
for listed := range ch.UserList.IterBuffered() {
ui := listed.Val
user, usrok := ui.(*User)
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)
@ -328,30 +334,17 @@ func (ch Channel) Admins(c *Client) []*User {
}
// 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) {
return
}
ch.UserList = append(ch.UserList, ToRFC1459(nick))
sort.Strings(ch.UserList)
ch.UserList.Set(ToRFC1459(nick), usr)
}
// deleteUser removes an existing user from the users list.
func (ch *Channel) deleteUser(nick string) {
nick = ToRFC1459(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:]...)
}
ch.UserList.Remove(nick)
}
// 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.
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.
func (ch *Channel) UserIn(name string) bool {
name = ToRFC1459(name)
for i := 0; i < len(ch.UserList); i++ {
if ch.UserList[i] == name {
return true
}
}
return false
return ch.UserList.Has(name)
}
// 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{
Name: name,
UserList: []string{},
UserList: cmap.New(),
Joined: time.Now(),
Network: s.client.NetworkName(),
Modes: NewCModes(supported, prefixes),
@ -427,10 +413,12 @@ func (s *state) deleteChannel(name string) {
chn := c.(*Channel)
for _, user := range chn.UserList {
ui, _ := s.users.Get(user)
usr := ui.(*User)
usr.deleteChannel(name)
for listed := range chn.UserList.IterBuffered() {
ui, _ := s.users.Get(listed.Key)
usr, usrok := ui.(*User)
if usrok {
usr.deleteChannel(name)
}
}
s.channels.Remove(name)
@ -465,14 +453,15 @@ func (s *state) createUser(src *Source) (u *User, ok bool) {
}
u = &User{
Nick: src.Name,
Host: src.Host,
Ident: src.Ident,
Mask: src.Name + "!" + src.Ident + "@" + src.Host,
FirstSeen: time.Now(),
LastActive: time.Now(),
Network: s.client.NetworkName(),
Perms: &UserPerms{channels: make(map[string]Perms)},
Nick: src.Name,
Host: src.Host,
Ident: src.Ident,
Mask: src.Name + "!" + src.Ident + "@" + src.Host,
ChannelList: cmap.New(),
FirstSeen: time.Now(),
LastActive: time.Now(),
Network: s.client.NetworkName(),
Perms: &UserPerms{channels: make(map[string]Perms)},
}
s.users.Set(src.ID(), u)
@ -488,13 +477,11 @@ func (s *state) deleteUser(channelName, nick string) {
}
if channelName == "" {
for i := 0; i < len(user.ChannelList); i++ {
ci, _ := s.channels.Get(user.ChannelList[i])
chn := ci.(*Channel)
chn.deleteUser(nick)
}
s.users.Remove(ToRFC1459(nick))
user.ChannelList.Clear()
// While we do still want to remove them from the channels,
// We want to hold onto that user object regardless on if they dip-set.
// s.users.Remove(ToRFC1459(nick))
user.Stale = true
return
}
@ -505,12 +492,8 @@ func (s *state) deleteUser(channelName, nick string) {
user.deleteChannel(channelName)
channel.deleteUser(nick)
if len(user.ChannelList) == 0 {
// This means they are no longer in any channels we track, delete
// them from state.
s.users.Remove(ToRFC1459(nick))
if user.ChannelList.Count() == 0 {
user.Stale = true
}
}
@ -524,11 +507,15 @@ func (s *state) renameUser(from, to string) {
}
user := s.lookupUser(from)
if user == nil {
old, oldok := s.users.Pop(from)
if !oldok && user == nil {
return
}
s.users.Remove(from)
if old != nil && user == nil {
user = old.(*User)
}
user.Nick = to
user.LastActive = time.Now()
@ -536,13 +523,12 @@ func (s *state) renameUser(from, to string) {
for chanchan := range s.channels.IterBuffered() {
chi := chanchan.Val
chn := chi.(*Channel)
for i := range chn.UserList {
if chn.UserList[i] == from {
chn.UserList[i] = ToRFC1459(to)
sort.Strings(chn.UserList)
break
}
chn, chok := chi.(*Channel)
if !chok {
continue
}
if old, oldok := chn.UserList.Pop(from); oldok {
chn.UserList.Set(to, old)
}
}
}

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