Merge pull request #3 from yunginnanet/cmap
This commit is contained in:
commit
12e6cfbe3b
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@ -22,4 +22,4 @@ jobs:
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
run: go test -race -v ./...
|
||||
|
24
builtin.go
24
builtin.go
@ -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
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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user