Fixed: functional with concurrent maps
This commit is contained in:
parent
23cea998f1
commit
b8186e2144
39
builtin.go
39
builtin.go
@ -12,17 +12,10 @@ import (
|
|||||||
"github.com/araddon/dateparse"
|
"github.com/araddon/dateparse"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
stateUnlocked uint32 = iota
|
|
||||||
stateLocked
|
|
||||||
)
|
|
||||||
|
|
||||||
// registerBuiltin sets up built-in handlers, based on client
|
// registerBuiltin sets up built-in handlers, based on client
|
||||||
// configuration.
|
// configuration.
|
||||||
func (c *Client) registerBuiltins() {
|
func (c *Client) registerBuiltins() {
|
||||||
c.debug.Print("registering built-in handlers")
|
c.debug.Print("registering built-in handlers")
|
||||||
c.Handlers.mu.Lock()
|
|
||||||
defer c.Handlers.mu.Unlock()
|
|
||||||
|
|
||||||
// Built-in things that should always be supported.
|
// Built-in things that should always be supported.
|
||||||
c.Handlers.register(true, true, RPL_WELCOME, HandlerFunc(handleConnect))
|
c.Handlers.register(true, true, RPL_WELCOME, HandlerFunc(handleConnect))
|
||||||
@ -107,10 +100,19 @@ func handleConnect(c *Client, e Event) {
|
|||||||
c.state.nick.Store(e.Params[0])
|
c.state.nick.Store(e.Params[0])
|
||||||
c.state.notify(c, UPDATE_GENERAL)
|
c.state.notify(c, UPDATE_GENERAL)
|
||||||
split := strings.Split(e.Params[1], " ")
|
split := strings.Split(e.Params[1], " ")
|
||||||
if strings.HasPrefix(e.Params[1], "Welcome to the") && len(split) > 3 {
|
search:
|
||||||
if len(split[3]) > 0 {
|
for i, artifact := range split {
|
||||||
c.state.network = split[3]
|
switch strings.ToLower(artifact) {
|
||||||
c.IRCd.Network = split[3]
|
case "welcome", "to":
|
||||||
|
continue
|
||||||
|
case "the":
|
||||||
|
if len(split) < i {
|
||||||
|
break search
|
||||||
|
}
|
||||||
|
c.IRCd.Network = split[i+1]
|
||||||
|
break search
|
||||||
|
default:
|
||||||
|
break search
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -221,9 +223,7 @@ func handlePART(c *Client, e Event) {
|
|||||||
defer c.state.notify(c, UPDATE_STATE)
|
defer c.state.notify(c, UPDATE_STATE)
|
||||||
|
|
||||||
if e.Source.ID() == c.GetID() {
|
if e.Source.ID() == c.GetID() {
|
||||||
|
|
||||||
c.state.deleteChannel(channel)
|
c.state.deleteChannel(channel)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,7 +376,6 @@ func handleNICK(c *Client, e Event) {
|
|||||||
if len(e.Params) >= 1 {
|
if len(e.Params) >= 1 {
|
||||||
c.state.renameUser(e.Source.ID(), e.Last())
|
c.state.renameUser(e.Source.ID(), e.Last())
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.notify(c, UPDATE_STATE)
|
c.state.notify(c, UPDATE_STATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,7 +403,6 @@ func handleGLOBALUSERS(c *Client, e Event) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IRCd.UserCount = cusers
|
c.IRCd.UserCount = cusers
|
||||||
c.IRCd.MaxUserCount = musers
|
c.IRCd.MaxUserCount = musers
|
||||||
}
|
}
|
||||||
@ -418,7 +416,6 @@ func handleLOCALUSERS(c *Client, e Event) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IRCd.LocalUserCount = cusers
|
c.IRCd.LocalUserCount = cusers
|
||||||
c.IRCd.LocalMaxUserCount = musers
|
c.IRCd.LocalMaxUserCount = musers
|
||||||
}
|
}
|
||||||
@ -428,7 +425,6 @@ func handleLUSERCHANNELS(c *Client, e Event) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IRCd.ChannelCount = ccount
|
c.IRCd.ChannelCount = ccount
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,7 +433,6 @@ func handleLUSEROP(c *Client, e Event) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IRCd.OperCount = ocount
|
c.IRCd.OperCount = ocount
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -462,9 +457,7 @@ func handleCREATED(c *Client, e Event) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IRCd.Compiled = compiled
|
c.IRCd.Compiled = compiled
|
||||||
|
|
||||||
c.state.notify(c, UPDATE_GENERAL)
|
c.state.notify(c, UPDATE_GENERAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -484,10 +477,8 @@ func handleYOURHOST(c *Client, e Event) {
|
|||||||
if len(host)+len(ver) == 0 {
|
if len(host)+len(ver) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IRCd.Host = host
|
c.IRCd.Host = host
|
||||||
c.IRCd.Version = ver
|
c.IRCd.Version = ver
|
||||||
|
|
||||||
c.state.notify(c, UPDATE_GENERAL)
|
c.state.notify(c, UPDATE_GENERAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -533,13 +524,11 @@ func handleISUPPORT(c *Client, e Event) {
|
|||||||
// handleMOTD handles incoming MOTD messages and buffers them up for use with
|
// handleMOTD handles incoming MOTD messages and buffers them up for use with
|
||||||
// Client.ServerMOTD().
|
// Client.ServerMOTD().
|
||||||
func handleMOTD(c *Client, e Event) {
|
func handleMOTD(c *Client, e Event) {
|
||||||
|
|
||||||
defer c.state.notify(c, UPDATE_GENERAL)
|
defer c.state.notify(c, UPDATE_GENERAL)
|
||||||
|
|
||||||
// Beginning of the MOTD.
|
// Beginning of the MOTD.
|
||||||
if e.Command == RPL_MOTDSTART {
|
if e.Command == RPL_MOTDSTART {
|
||||||
c.state.motd = ""
|
c.state.motd = ""
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,7 +537,6 @@ func handleMOTD(c *Client, e Event) {
|
|||||||
c.state.motd += "\n"
|
c.state.motd += "\n"
|
||||||
}
|
}
|
||||||
c.state.motd += e.Last()
|
c.state.motd += e.Last()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleNAMES handles incoming NAMES queries, of which lists all users in
|
// handleNAMES handles incoming NAMES queries, of which lists all users in
|
||||||
@ -608,7 +596,6 @@ func handleNAMES(c *Client, e Event) {
|
|||||||
perms.set(modes, false)
|
perms.set(modes, false)
|
||||||
user.Perms.set(channel.Name, perms)
|
user.Perms.set(channel.Name, perms)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.notify(c, UPDATE_STATE)
|
c.state.notify(c, UPDATE_STATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
cap.go
36
cap.go
@ -9,6 +9,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
cmap "github.com/orcaman/concurrent-map"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Something not in the list? Depending on the type of capability, you can
|
// Something not in the list? Depending on the type of capability, you can
|
||||||
@ -118,12 +120,11 @@ func parseCap(raw string) map[string]map[string]string {
|
|||||||
// This will lock further registration until we have acknowledged (or denied)
|
// This will lock further registration until we have acknowledged (or denied)
|
||||||
// the capabilities.
|
// the capabilities.
|
||||||
func handleCAP(c *Client, e Event) {
|
func handleCAP(c *Client, e Event) {
|
||||||
|
|
||||||
if len(e.Params) >= 2 && e.Params[1] == CAP_DEL {
|
if len(e.Params) >= 2 && e.Params[1] == CAP_DEL {
|
||||||
caps := parseCap(e.Last())
|
caps := parseCap(e.Last())
|
||||||
for capab := range caps {
|
for capab := range caps {
|
||||||
// TODO: test the deletion.
|
// TODO: test the deletion.
|
||||||
delete(c.state.enabledCap, capab)
|
c.state.enabledCap.Remove(capab)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -146,7 +147,7 @@ func handleCAP(c *Client, e Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(possible[capName]) == 0 || len(caps[capName]) == 0 {
|
if len(possible[capName]) == 0 || len(caps[capName]) == 0 {
|
||||||
c.state.tmpCap[capName] = caps[capName]
|
c.state.tmpCap.Set(capName, caps[capName])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +168,7 @@ func handleCAP(c *Client, e Event) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.tmpCap[capName] = caps[capName]
|
c.state.tmpCap.Set(capName, caps[capName])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Indicates if this is a multi-line LS. (3 args means it's the
|
// Indicates if this is a multi-line LS. (3 args means it's the
|
||||||
@ -180,10 +181,11 @@ func handleCAP(c *Client, e Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Let them know which ones we'd like to enable.
|
// Let them know which ones we'd like to enable.
|
||||||
reqKeys := make([]string, len(c.state.tmpCap))
|
reqKeys := make([]string, len(c.state.tmpCap.Keys()))
|
||||||
i := 0
|
i := 0
|
||||||
for k := range c.state.tmpCap {
|
for k := range c.state.tmpCap.IterBuffered() {
|
||||||
reqKeys[i] = k
|
kv := k.Val.(string)
|
||||||
|
reqKeys[i] = kv
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
c.write(&Event{Command: CAP, Params: []string{CAP_REQ, strings.Join(reqKeys, " ")}})
|
c.write(&Event{Command: CAP, Params: []string{CAP_REQ, strings.Join(reqKeys, " ")}})
|
||||||
@ -193,10 +195,12 @@ func handleCAP(c *Client, e Event) {
|
|||||||
if len(e.Params) == 3 && e.Params[1] == CAP_ACK {
|
if len(e.Params) == 3 && e.Params[1] == CAP_ACK {
|
||||||
enabled := strings.Split(e.Last(), " ")
|
enabled := strings.Split(e.Last(), " ")
|
||||||
for _, capab := range enabled {
|
for _, capab := range enabled {
|
||||||
if val, ok := c.state.tmpCap[capab]; ok {
|
val, ok := c.state.tmpCap.Get(capab)
|
||||||
c.state.enabledCap[capab] = val
|
if ok {
|
||||||
|
val = val.(map[string]string)
|
||||||
|
c.state.enabledCap.Set(capab, val)
|
||||||
} else {
|
} else {
|
||||||
c.state.enabledCap[capab] = nil
|
c.state.enabledCap.Set(capab, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,9 +209,10 @@ func handleCAP(c *Client, e Event) {
|
|||||||
|
|
||||||
// Handle STS, and only if it's something specifically we enabled (client
|
// Handle STS, and only if it's something specifically we enabled (client
|
||||||
// may choose to disable girc automatic STS, and do it themselves).
|
// may choose to disable girc automatic STS, and do it themselves).
|
||||||
if sts, sok := c.state.enabledCap["sts"]; sok && !c.Config.DisableSTS {
|
stsi, sok := c.state.enabledCap.Get("sts")
|
||||||
|
if sok && !c.Config.DisableSTS {
|
||||||
var isError bool
|
var isError bool
|
||||||
|
sts := stsi.(map[string]string)
|
||||||
// Some things are updated in the policy depending on if the current
|
// Some things are updated in the policy depending on if the current
|
||||||
// connection is over tls or not.
|
// connection is over tls or not.
|
||||||
var hasTLSConnection bool
|
var hasTLSConnection bool
|
||||||
@ -284,9 +289,10 @@ func handleCAP(c *Client, e Event) {
|
|||||||
|
|
||||||
// Re-initialize the tmpCap, so if we get multiple 'CAP LS' requests
|
// Re-initialize the tmpCap, so if we get multiple 'CAP LS' requests
|
||||||
// due to cap-notify, we can re-evaluate what we can support.
|
// due to cap-notify, we can re-evaluate what we can support.
|
||||||
c.state.tmpCap = make(map[string]map[string]string)
|
c.state.tmpCap = cmap.New()
|
||||||
|
|
||||||
if _, ok := c.state.enabledCap["sasl"]; ok && c.Config.SASL != nil {
|
_, ok := c.state.enabledCap.Get("sasl")
|
||||||
|
if ok && c.Config.SASL != nil {
|
||||||
c.write(&Event{Command: AUTHENTICATE, Params: []string{c.Config.SASL.Method()}})
|
c.write(&Event{Command: AUTHENTICATE, Params: []string{c.Config.SASL.Method()}})
|
||||||
// Don't "CAP END", since we want to authenticate.
|
// Don't "CAP END", since we want to authenticate.
|
||||||
return
|
return
|
||||||
@ -342,11 +348,9 @@ func handleACCOUNT(c *Client, e Event) {
|
|||||||
account = ""
|
account = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.Lock()
|
|
||||||
user := c.state.lookupUser(e.Source.Name)
|
user := c.state.lookupUser(e.Source.Name)
|
||||||
if user != nil {
|
if user != nil {
|
||||||
user.Extras.Account = account
|
user.Extras.Account = account
|
||||||
}
|
}
|
||||||
c.state.Unlock()
|
|
||||||
c.state.notify(c, UPDATE_STATE)
|
c.state.notify(c, UPDATE_STATE)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
package girc
|
package girc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@ -16,7 +17,7 @@ func TestCapSupported(t *testing.T) {
|
|||||||
User: "user",
|
User: "user",
|
||||||
SASL: &SASLPlain{User: "test", Pass: "example"},
|
SASL: &SASLPlain{User: "test", Pass: "example"},
|
||||||
SupportedCaps: map[string][]string{"example": nil},
|
SupportedCaps: map[string][]string{"example": nil},
|
||||||
Debug: newDebugWriter(t),
|
Debug: os.Stdout,
|
||||||
})
|
})
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
|
17
client.go
17
client.go
@ -19,7 +19,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
cmap "github.com/orcaman/concurrent-map"
|
cmap "github.com/orcaman/concurrent-map"
|
||||||
@ -297,7 +296,6 @@ func New(config Config) *Client {
|
|||||||
tx: make(chan *Event, 25),
|
tx: make(chan *Event, 25),
|
||||||
CTCP: newCTCP(),
|
CTCP: newCTCP(),
|
||||||
initTime: time.Now(),
|
initTime: time.Now(),
|
||||||
atom: stateUnlocked,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IRCd = Server{
|
c.IRCd = Server{
|
||||||
@ -341,6 +339,7 @@ func New(config Config) *Client {
|
|||||||
|
|
||||||
// Give ourselves a new state.
|
// Give ourselves a new state.
|
||||||
c.state = &state{}
|
c.state = &state{}
|
||||||
|
c.state.RWMutex = &sync.RWMutex{}
|
||||||
c.state.reset(true)
|
c.state.reset(true)
|
||||||
|
|
||||||
c.state.client = c
|
c.state.client = c
|
||||||
@ -596,9 +595,12 @@ func (c *Client) GetHost() (host string) {
|
|||||||
func (c *Client) ChannelList() []string {
|
func (c *Client) ChannelList() []string {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
|
|
||||||
channels := make([]string, 0, len(c.state.channels))
|
channels := make([]string, 0, len(c.state.channels.Keys()))
|
||||||
for channel := range c.state.channels.IterBuffered() {
|
for channel := range c.state.channels.IterBuffered() {
|
||||||
chn := channel.Val.(*Channel)
|
chn := channel.Val.(*Channel)
|
||||||
|
if !chn.UserIn(c.GetNick()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
channels = append(channels, chn.Name)
|
channels = append(channels, chn.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -729,7 +731,7 @@ func (c *Client) NetworkName() (name string) {
|
|||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
if len(c.state.network) > 0 {
|
if len(c.state.network) > 0 {
|
||||||
return
|
return c.state.network
|
||||||
}
|
}
|
||||||
|
|
||||||
name, ok = c.GetServerOption("NETWORK")
|
name, ok = c.GetServerOption("NETWORK")
|
||||||
@ -788,12 +790,7 @@ func (c *Client) HasCapability(name string) (has bool) {
|
|||||||
|
|
||||||
name = strings.ToLower(name)
|
name = strings.ToLower(name)
|
||||||
|
|
||||||
for atomic.CompareAndSwapUint32(&c.atom, stateUnlocked, stateLocked) {
|
for _, key := range c.state.enabledCap.Keys() {
|
||||||
randSleep()
|
|
||||||
}
|
|
||||||
defer atomic.StoreUint32(&c.atom, stateUnlocked)
|
|
||||||
|
|
||||||
for key := range c.state.enabledCap {
|
|
||||||
key = strings.ToLower(key)
|
key = strings.ToLower(key)
|
||||||
if key == name {
|
if key == name {
|
||||||
has = true
|
has = true
|
||||||
|
@ -5,24 +5,12 @@
|
|||||||
package girc
|
package girc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type debugWriter struct {
|
|
||||||
t *testing.T
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDebugWriter(t *testing.T) debugWriter {
|
|
||||||
return debugWriter{t: t}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d debugWriter) Write(p []byte) (n int, err error) {
|
|
||||||
go d.t.Logf("%v", string(p))
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDisableTracking(t *testing.T) {
|
func TestDisableTracking(t *testing.T) {
|
||||||
client := New(Config{
|
client := New(Config{
|
||||||
Server: "dummy.int",
|
Server: "dummy.int",
|
||||||
@ -30,7 +18,7 @@ func TestDisableTracking(t *testing.T) {
|
|||||||
Nick: "test",
|
Nick: "test",
|
||||||
User: "test",
|
User: "test",
|
||||||
Name: "Testing123",
|
Name: "Testing123",
|
||||||
Debug: newDebugWriter(t),
|
Debug: os.Stdout,
|
||||||
})
|
})
|
||||||
|
|
||||||
if client.Handlers.internal.len() < 1 {
|
if client.Handlers.internal.len() < 1 {
|
||||||
@ -96,7 +84,7 @@ func TestClientLifetime(t *testing.T) {
|
|||||||
Nick: "test",
|
Nick: "test",
|
||||||
User: "test",
|
User: "test",
|
||||||
Name: "Testing123",
|
Name: "Testing123",
|
||||||
Debug: newDebugWriter(t),
|
Debug: os.Stdout,
|
||||||
})
|
})
|
||||||
|
|
||||||
tm := client.Lifetime()
|
tm := client.Lifetime()
|
||||||
@ -107,7 +95,7 @@ func TestClientLifetime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestClientUptime(t *testing.T) {
|
func TestClientUptime(t *testing.T) {
|
||||||
c, conn, server := genMockConn(t)
|
c, conn, server := genMockConn()
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
go mockReadBuffer(conn)
|
go mockReadBuffer(conn)
|
||||||
@ -152,7 +140,7 @@ func TestClientUptime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestClientGet(t *testing.T) {
|
func TestClientGet(t *testing.T) {
|
||||||
c, conn, server := genMockConn(t)
|
c, conn, server := genMockConn()
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
go mockReadBuffer(conn)
|
go mockReadBuffer(conn)
|
||||||
@ -183,7 +171,7 @@ func TestClientGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestClientClose(t *testing.T) {
|
func TestClientClose(t *testing.T) {
|
||||||
c, conn, server := genMockConn(t)
|
c, conn, server := genMockConn()
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
go mockReadBuffer(conn)
|
go mockReadBuffer(conn)
|
||||||
|
@ -1,191 +0,0 @@
|
|||||||
package cmdhandler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/yunginnanet/girc-atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Input is a wrapper for events, based around private messages.
|
|
||||||
type Input struct {
|
|
||||||
Origin *girc.Event
|
|
||||||
Args []string
|
|
||||||
RawArgs string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Command is an IRC command, supporting aliases, help documentation and easy
|
|
||||||
// wrapping for message inputs.
|
|
||||||
type Command struct {
|
|
||||||
// Name of command, e.g. "search" or "ping".
|
|
||||||
Name string
|
|
||||||
// Aliases for the above command, e.g. "s" for search, or "p" for "ping".
|
|
||||||
Aliases []string
|
|
||||||
// Help documentation. Should be in the format "<arg> <arg> [arg] --
|
|
||||||
// something useful here"
|
|
||||||
Help string
|
|
||||||
// MinArgs is the minimum required arguments for the command. Defaults to
|
|
||||||
// 0, which means multiple, or no arguments can be supplied. If set
|
|
||||||
// above 0, this means that the command handler will throw an error asking
|
|
||||||
// the person to check "<prefix>help <command>" for more info.
|
|
||||||
MinArgs int
|
|
||||||
// Fn is the function which is executed when the command is ran from a
|
|
||||||
// private message, or channel.
|
|
||||||
Fn func(*girc.Client, *Input)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Command) genHelp(prefix string) string {
|
|
||||||
out := "{b}" + prefix + c.Name + "{b}"
|
|
||||||
|
|
||||||
if c.Aliases != nil && len(c.Aliases) > 0 {
|
|
||||||
out += " ({b}" + prefix + strings.Join(c.Aliases, "{b}, {b}"+prefix) + "{b})"
|
|
||||||
}
|
|
||||||
|
|
||||||
out += " :: " + c.Help
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdHandler is an irc command parser and execution format which you could
|
|
||||||
// use as an example for building your own version/bot.
|
|
||||||
//
|
|
||||||
// An example of how you would register this with girc:
|
|
||||||
//
|
|
||||||
// ch, err := cmdhandler.New("!")
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ch.Add(&cmdhandler.Command{
|
|
||||||
// Name: "ping",
|
|
||||||
// Help: "Sends a pong reply back to the original user.",
|
|
||||||
// Fn: func(c *girc.Client, input *cmdhandler.Input) {
|
|
||||||
// c.Commands.ReplyTo(*input.Origin, "pong!")
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// client.Handlers.AddHandler(girc.PRIVMSG, ch)
|
|
||||||
type CmdHandler struct {
|
|
||||||
prefix string
|
|
||||||
re *regexp.Regexp
|
|
||||||
|
|
||||||
cmds map[string]*Command
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmdMatch = `^%s([a-z0-9-_]{1,20})(?: (.*))?$`
|
|
||||||
|
|
||||||
// New returns a new CmdHandler based on the specified command prefix. A good
|
|
||||||
// prefix is a single character, and easy to remember/use. E.g. "!", or ".".
|
|
||||||
func New(prefix string) (*CmdHandler, error) {
|
|
||||||
re, err := regexp.Compile(fmt.Sprintf(cmdMatch, regexp.QuoteMeta(prefix)))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CmdHandler{prefix: prefix, re: re, cmds: make(map[string]*Command)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var validName = regexp.MustCompile(`^[a-z0-9-_]{1,20}$`)
|
|
||||||
|
|
||||||
// Add registers a new command to the handler. Note that you cannot remove
|
|
||||||
// commands once added, unless you add another CmdHandler to the client.
|
|
||||||
func (ch *CmdHandler) Add(cmd *Command) error {
|
|
||||||
if cmd == nil {
|
|
||||||
return errors.New("nil command provided to CmdHandler")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Name = strings.ToLower(cmd.Name)
|
|
||||||
if !validName.MatchString(cmd.Name) {
|
|
||||||
return fmt.Errorf("invalid command name: %q (req: %q)", cmd.Name, validName.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.Aliases != nil {
|
|
||||||
for i := 0; i < len(cmd.Aliases); i++ {
|
|
||||||
cmd.Aliases[i] = strings.ToLower(cmd.Aliases[i])
|
|
||||||
if !validName.MatchString(cmd.Aliases[i]) {
|
|
||||||
return fmt.Errorf("invalid command name: %q (req: %q)", cmd.Aliases[i], validName.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.MinArgs < 0 {
|
|
||||||
cmd.MinArgs = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := ch.cmds[cmd.Name]; ok {
|
|
||||||
return fmt.Errorf("command already registered: %s", cmd.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
ch.cmds[cmd.Name] = cmd
|
|
||||||
|
|
||||||
// Since we'd be storing pointers, duplicates do not matter.
|
|
||||||
for i := 0; i < len(cmd.Aliases); i++ {
|
|
||||||
if _, ok := ch.cmds[cmd.Aliases[i]]; ok {
|
|
||||||
return fmt.Errorf("alias already registered: %s", cmd.Aliases[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
ch.cmds[cmd.Aliases[i]] = cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute satisfies the girc.Handler interface.
|
|
||||||
func (ch *CmdHandler) Execute(client *girc.Client, event girc.Event) {
|
|
||||||
if event.Source == nil || event.Command != girc.PRIVMSG {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed := ch.re.FindStringSubmatch(event.Last())
|
|
||||||
if len(parsed) != 3 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
invCmd := strings.ToLower(parsed[1])
|
|
||||||
args := strings.Split(parsed[2], " ")
|
|
||||||
if len(args) == 1 && args[0] == "" {
|
|
||||||
args = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if invCmd == "help" {
|
|
||||||
if len(args) == 0 {
|
|
||||||
client.Cmd.ReplyTo(event, girc.Fmt("type '{b}!help {blue}<command>{c}{b}' to optionally get more info about a specific command."))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
args[0] = strings.ToLower(args[0])
|
|
||||||
|
|
||||||
if _, ok := ch.cmds[args[0]]; !ok {
|
|
||||||
client.Cmd.ReplyTof(event, girc.Fmt("unknown command {b}%q{b}."), args[0])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ch.cmds[args[0]].Help == "" {
|
|
||||||
client.Cmd.ReplyTof(event, girc.Fmt("there is no help documentation for {b}%q{b}"), args[0])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client.Cmd.ReplyTo(event, girc.Fmt(ch.cmds[args[0]].genHelp(ch.prefix)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd, ok := ch.cmds[invCmd]
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) < cmd.MinArgs {
|
|
||||||
client.Cmd.ReplyTof(event, girc.Fmt("not enough arguments supplied for {b}%q{b}. try '{b}%shelp %s{b}'?"), invCmd, ch.prefix, invCmd)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
in := &Input{
|
|
||||||
Origin: &event,
|
|
||||||
Args: args,
|
|
||||||
RawArgs: parsed[2],
|
|
||||||
}
|
|
||||||
|
|
||||||
go cmd.Fn(client, in)
|
|
||||||
}
|
|
11
conn.go
11
conn.go
@ -441,11 +441,6 @@ func (c *Client) Send(event *Event) {
|
|||||||
|
|
||||||
event.Network = c.NetworkName()
|
event.Network = c.NetworkName()
|
||||||
|
|
||||||
for atomic.CompareAndSwapUint32(&c.atom, stateUnlocked, stateLocked) {
|
|
||||||
randSleep()
|
|
||||||
}
|
|
||||||
defer atomic.StoreUint32(&c.atom, stateUnlocked)
|
|
||||||
|
|
||||||
if !c.Config.AllowFlood {
|
if !c.Config.AllowFlood {
|
||||||
// Drop the event early as we're disconnected, this way we don't have to wait
|
// Drop the event early as we're disconnected, this way we don't have to wait
|
||||||
// the (potentially long) rate limit delay before dropping.
|
// the (potentially long) rate limit delay before dropping.
|
||||||
@ -515,7 +510,7 @@ func (c *Client) sendLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
|
|||||||
//
|
//
|
||||||
var in bool
|
var in bool
|
||||||
for i := 0; i < len(c.state.enabledCap); i++ {
|
for i := 0; i < len(c.state.enabledCap); i++ {
|
||||||
if _, ok := c.state.enabledCap["message-tags"]; ok {
|
if _, ok := c.state.enabledCap.Get("message-tags"); ok {
|
||||||
in = true
|
in = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -580,9 +575,9 @@ type ErrTimedOut struct {
|
|||||||
func (ErrTimedOut) Error() string { return "timed out waiting for a requested PING response" }
|
func (ErrTimedOut) Error() string { return "timed out waiting for a requested PING response" }
|
||||||
|
|
||||||
func (c *Client) pingLoop(ctx context.Context, errs chan error, wg *sync.WaitGroup) {
|
func (c *Client) pingLoop(ctx context.Context, errs chan error, wg *sync.WaitGroup) {
|
||||||
defer wg.Done()
|
|
||||||
// Don't run the pingLoop if they want to disable it.
|
// Don't run the pingLoop if they want to disable it.
|
||||||
if c.Config.PingDelay <= 0 {
|
if c.Config.PingDelay <= 0 {
|
||||||
|
wg.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -621,6 +616,7 @@ func (c *Client) pingLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
|
|||||||
Delay: c.Config.PingDelay,
|
Delay: c.Config.PingDelay,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,6 +624,7 @@ func (c *Client) pingLoop(ctx context.Context, errs chan error, wg *sync.WaitGro
|
|||||||
|
|
||||||
c.Cmd.Ping(fmt.Sprintf("%d", time.Now().UnixNano()))
|
c.Cmd.Ping(fmt.Sprintf("%d", time.Now().UnixNano()))
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
wg.Done()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
conn_test.go
18
conn_test.go
@ -8,6 +8,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -93,14 +94,14 @@ func TestRate(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func genMockConn(t *testing.T) (client *Client, clientConn net.Conn, serverConn net.Conn) {
|
func genMockConn() (client *Client, clientConn net.Conn, serverConn net.Conn) {
|
||||||
client = New(Config{
|
client = New(Config{
|
||||||
Server: "dummy.int",
|
Server: "dummy.int",
|
||||||
Port: 6667,
|
Port: 6667,
|
||||||
Nick: "test",
|
Nick: "test",
|
||||||
User: "test",
|
User: "test",
|
||||||
Name: "Testing123",
|
Name: "Testing123",
|
||||||
Debug: newDebugWriter(t),
|
Debug: os.Stdout,
|
||||||
})
|
})
|
||||||
|
|
||||||
conn1, conn2 := net.Pipe()
|
conn1, conn2 := net.Pipe()
|
||||||
@ -108,19 +109,14 @@ func genMockConn(t *testing.T) (client *Client, clientConn net.Conn, serverConn
|
|||||||
return client, conn1, conn2
|
return client, conn1, conn2
|
||||||
}
|
}
|
||||||
|
|
||||||
func mockReadBuffer(conn net.Conn) error {
|
func mockReadBuffer(conn net.Conn) {
|
||||||
// Accept all outgoing writes from the client.
|
// Accept all outgoing writes from the client.
|
||||||
b := bufio.NewReader(conn)
|
b := bufio.NewReader(conn)
|
||||||
for {
|
for {
|
||||||
err := conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
_, err := b.ReadString(byte('\n'))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return
|
||||||
}
|
|
||||||
var str string
|
|
||||||
str, err = b.ReadString(byte('\n'))
|
|
||||||
println(str)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
118
handler.go
118
handler.go
@ -167,6 +167,7 @@ func (c *Caller) Len() int {
|
|||||||
// Count is much like Caller.Len(), however it counts the number of
|
// Count is much like Caller.Len(), however it counts the number of
|
||||||
// registered handlers for a given command.
|
// registered handlers for a given command.
|
||||||
func (c *Caller) Count(cmd string) int {
|
func (c *Caller) Count(cmd string) int {
|
||||||
|
cmd = strings.ToUpper(cmd)
|
||||||
return c.external.lenFor(cmd)
|
return c.external.lenFor(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,63 +199,91 @@ func (c *Caller) cuidToID(input string) (cmd, uid string) {
|
|||||||
return input[:i], input[i+1:]
|
return input[:i], input[i+1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type execStack struct {
|
||||||
|
Handler
|
||||||
|
cuid string
|
||||||
|
}
|
||||||
|
|
||||||
// exec executes all handlers pertaining to specified event. Internal first,
|
// exec executes all handlers pertaining to specified event. Internal first,
|
||||||
// then external.
|
// then external.
|
||||||
//
|
//
|
||||||
// Please note that there is no specific order/priority for which the handlers
|
// Please note that there is no specific order/priority for which the handlers
|
||||||
// are executed.
|
// are executed.
|
||||||
func (c *Caller) exec(command string, bg bool, client *Client, event *Event) {
|
func (c *Caller) exec(command string, bg bool, client *Client, event *Event) {
|
||||||
handle := func(wgr *sync.WaitGroup, h handlerTuple) {
|
|
||||||
|
|
||||||
c.debug.Printf("(%s) exec %s => %s", c.parent.Config.Nick, command, h.cuid)
|
// Build a stack of handlers which can be executed concurrently.
|
||||||
start := time.Now()
|
var stack []execStack
|
||||||
|
|
||||||
if bg {
|
// Get internal handlers first.
|
||||||
go func() {
|
ihm, iok := c.internal.cm.Get(command)
|
||||||
defer wgr.Done()
|
if iok {
|
||||||
if client.Config.RecoverFunc != nil {
|
hmap := ihm.(cmap.ConcurrentMap)
|
||||||
defer recoverHandlerPanic(client, event, h.cuid, 3)
|
for _, cuid := range hmap.Keys() {
|
||||||
}
|
if (strings.HasSuffix(cuid, ":bg") && !bg) || (!strings.HasSuffix(cuid, ":bg") && bg) {
|
||||||
h.handler.Execute(client, *event)
|
continue
|
||||||
c.debug.Printf("(%s) done %s == %s", c.parent.Config.Nick,
|
}
|
||||||
h.cuid, time.Since(start))
|
hi, _ := hmap.Get(cuid)
|
||||||
}()
|
hndlr, ok := hi.(Handler)
|
||||||
return
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stack = append(stack, execStack{hndlr, cuid})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if client.Config.RecoverFunc != nil {
|
// Then external handlers.
|
||||||
defer recoverHandlerPanic(client, event, h.cuid, 3)
|
ehm, eok := c.external.cm.Get(command)
|
||||||
|
if eok {
|
||||||
|
hmap := ehm.(cmap.ConcurrentMap)
|
||||||
|
for _, cuid := range hmap.Keys() {
|
||||||
|
if (strings.HasSuffix(cuid, ":bg") && !bg) || (!strings.HasSuffix(cuid, ":bg") && bg) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hi, _ := hmap.Get(cuid)
|
||||||
|
hndlr, ok := hi.(Handler)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stack = append(stack, execStack{hndlr, cuid})
|
||||||
}
|
}
|
||||||
|
|
||||||
h.handler.Execute(client, *event)
|
|
||||||
c.debug.Printf("(%s) done %s == %s", c.parent.Config.Nick, h.cuid, time.Since(start))
|
|
||||||
wgr.Done()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run all handlers concurrently across the same event. This should
|
// Run all handlers concurrently across the same event. This should
|
||||||
// still help prevent mis-ordered events, while speeding up the
|
// still help prevent mis-ordered events, while speeding up the
|
||||||
// execution speed.
|
// execution speed.
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(stack))
|
||||||
|
for i := 0; i < len(stack); i++ {
|
||||||
|
go func(index int) {
|
||||||
|
c.debug.Printf("(%s) [%d/%d] exec %s => %s", c.parent.Config.Nick,
|
||||||
|
index+1, len(stack), stack[index].cuid, command)
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
internals, iok := c.internal.getAllHandlersFor(command)
|
if bg {
|
||||||
if iok {
|
go func() {
|
||||||
for h := range internals {
|
defer wg.Done()
|
||||||
wg.Add(1)
|
if client.Config.RecoverFunc != nil {
|
||||||
go handle(&wg, h)
|
defer recoverHandlerPanic(client, event, stack[index].cuid, 3)
|
||||||
}
|
}
|
||||||
}
|
stack[index].Handler.Execute(client, *event)
|
||||||
externals, eok := c.external.getAllHandlersFor(command)
|
c.debug.Printf("(%s) done %s == %s", c.parent.Config.Nick,
|
||||||
if eok {
|
stack[index].cuid, time.Since(start))
|
||||||
for h := range externals {
|
}()
|
||||||
wg.Add(1)
|
return
|
||||||
go handle(&wg, h)
|
}
|
||||||
}
|
defer wg.Done()
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for all of the handlers to complete. Not doing this may cause
|
if client.Config.RecoverFunc != nil {
|
||||||
// new events from becoming ahead of older handlers.
|
defer recoverHandlerPanic(client, event, stack[index].cuid, 3)
|
||||||
c.debug.Printf("(%s) wg.Wait()", c.parent.Config.Nick)
|
}
|
||||||
wg.Wait()
|
|
||||||
|
stack[index].Handler.Execute(client, *event)
|
||||||
|
c.debug.Printf("(%s) done %s == %s", c.parent.Config.Nick, stack[index].cuid, time.Since(start))
|
||||||
|
}(i)
|
||||||
|
|
||||||
|
// new events from becoming ahead of ol1 handlers.
|
||||||
|
c.debug.Printf("(%s) wg.Wait()", c.parent.Config.Nick)
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearAll clears all external handlers currently setup within the client.
|
// ClearAll clears all external handlers currently setup within the client.
|
||||||
@ -283,11 +312,8 @@ func (c *Caller) Clear(cmd string) {
|
|||||||
// indicates that it existed, and has been removed. If not success, it
|
// indicates that it existed, and has been removed. If not success, it
|
||||||
// wasn't a registered handler.
|
// wasn't a registered handler.
|
||||||
func (c *Caller) Remove(cuid string) (success bool) {
|
func (c *Caller) Remove(cuid string) (success bool) {
|
||||||
c.mu.Lock()
|
c.remove(cuid)
|
||||||
success = c.remove(cuid)
|
return true
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
return success
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove is much like Remove, however is NOT concurrency safe. Lock Caller.mu
|
// remove is much like Remove, however is NOT concurrency safe. Lock Caller.mu
|
||||||
@ -359,12 +385,12 @@ func (c *Caller) register(internal, bg bool, cmd string, handler Handler) (cuid
|
|||||||
} else {
|
} else {
|
||||||
chandlers = cmap.New()
|
chandlers = cmap.New()
|
||||||
}
|
}
|
||||||
parent.cm.SetIfAbsent(cmd, chandlers)
|
|
||||||
|
|
||||||
chandlers.Set(uid, handler)
|
chandlers.Set(uid, handler)
|
||||||
|
|
||||||
_, file, line, _ := runtime.Caller(2)
|
parent.cm.Set(cmd, chandlers)
|
||||||
|
|
||||||
|
_, file, line, _ := runtime.Caller(2)
|
||||||
c.debug.Printf("reg %q => %s [int:%t bg:%t] %s:%d", uid, cmd, internal, bg, file, line)
|
c.debug.Printf("reg %q => %s [int:%t bg:%t] %s:%d", uid, cmd, internal, bg, file, line)
|
||||||
|
|
||||||
return cuid
|
return cuid
|
||||||
|
6
modes.go
6
modes.go
@ -404,13 +404,9 @@ func (p *UserPerms) Copy() (perms *UserPerms) {
|
|||||||
np := &UserPerms{
|
np := &UserPerms{
|
||||||
channels: make(map[string]Perms),
|
channels: make(map[string]Perms),
|
||||||
}
|
}
|
||||||
|
|
||||||
p.mu.RLock()
|
|
||||||
for key := range p.channels {
|
for key := range p.channels {
|
||||||
np.channels[key] = p.channels[key]
|
np.channels[key] = p.channels[key]
|
||||||
}
|
}
|
||||||
p.mu.RUnlock()
|
|
||||||
|
|
||||||
return np
|
return np
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -426,9 +422,7 @@ func (p *UserPerms) MarshalJSON() ([]byte, error) {
|
|||||||
// Lookup looks up the users permissions for a given channel. ok is false
|
// Lookup looks up the users permissions for a given channel. ok is false
|
||||||
// if the user is not in the given channel.
|
// if the user is not in the given channel.
|
||||||
func (p *UserPerms) Lookup(channel string) (perms Perms, ok bool) {
|
func (p *UserPerms) Lookup(channel string) (perms Perms, ok bool) {
|
||||||
p.mu.RLock()
|
|
||||||
perms, ok = p.channels[ToRFC1459(channel)]
|
perms, ok = p.channels[ToRFC1459(channel)]
|
||||||
p.mu.RUnlock()
|
|
||||||
|
|
||||||
return perms, ok
|
return perms, ok
|
||||||
}
|
}
|
||||||
|
9
state.go
9
state.go
@ -28,11 +28,11 @@ type state struct {
|
|||||||
// users map[string]*User
|
// users map[string]*User
|
||||||
users cmap.ConcurrentMap
|
users cmap.ConcurrentMap
|
||||||
// enabledCap are the capabilities which are enabled for this connection.
|
// enabledCap are the capabilities which are enabled for this connection.
|
||||||
enabledCap map[string]map[string]string
|
enabledCap cmap.ConcurrentMap
|
||||||
// tmpCap are the capabilties which we share with the server during the
|
// tmpCap are the capabilties which we share with the server during the
|
||||||
// last capability check. These will get sent once we have received the
|
// last capability check. These will get sent once we have received the
|
||||||
// last capability list command from the server.
|
// last capability list command from the server.
|
||||||
tmpCap map[string]map[string]string
|
tmpCap cmap.ConcurrentMap
|
||||||
// serverOptions are the standard capabilities and configurations
|
// serverOptions are the standard capabilities and configurations
|
||||||
// supported by the server at connection time. This also includes
|
// supported by the server at connection time. This also includes
|
||||||
// RPL_ISUPPORT entries.
|
// RPL_ISUPPORT entries.
|
||||||
@ -69,8 +69,8 @@ func (s *state) reset(initial bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.enabledCap = make(map[string]map[string]string)
|
s.enabledCap = cmap.New()
|
||||||
s.tmpCap = make(map[string]map[string]string)
|
s.tmpCap = cmap.New()
|
||||||
s.motd = ""
|
s.motd = ""
|
||||||
|
|
||||||
if initial {
|
if initial {
|
||||||
@ -482,6 +482,7 @@ func (s *state) createUser(src *Source) (u *User, ok bool) {
|
|||||||
func (s *state) deleteUser(channelName, nick string) {
|
func (s *state) deleteUser(channelName, nick string) {
|
||||||
user := s.lookupUser(nick)
|
user := s.lookupUser(nick)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
|
s.client.debug.Printf(nick + ": was not found when trying to deleteUser from " + channelName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
package girc
|
package girc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -22,6 +21,7 @@ func debounce(delay time.Duration, done chan bool, f func()) {
|
|||||||
f()
|
f()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,8 +57,7 @@ const mockConnEndState = `:nick2!nick2@other.int QUIT :example reason
|
|||||||
`
|
`
|
||||||
|
|
||||||
func TestState(t *testing.T) {
|
func TestState(t *testing.T) {
|
||||||
c, conn, server := genMockConn(t)
|
c, conn, server := genMockConn()
|
||||||
|
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
go mockReadBuffer(conn)
|
go mockReadBuffer(conn)
|
||||||
|
|
||||||
@ -73,57 +72,50 @@ func TestState(t *testing.T) {
|
|||||||
finishStart := make(chan bool, 1)
|
finishStart := make(chan bool, 1)
|
||||||
go debounce(250*time.Millisecond, bounceStart, func() {
|
go debounce(250*time.Millisecond, bounceStart, func() {
|
||||||
if motd := c.ServerMOTD(); motd != "example motd" {
|
if motd := c.ServerMOTD(); motd != "example motd" {
|
||||||
t.Errorf("Client.ServerMOTD() returned invalid MOTD: %q", motd)
|
t.Fatalf("Client.ServerMOTD() returned invalid MOTD: %q", motd)
|
||||||
}
|
}
|
||||||
|
|
||||||
if network := c.NetworkName(); network != "DummyIRC" && network != "DUMMY" {
|
if network := c.NetworkName(); network != "DummyIRC" && network != "DUMMY" {
|
||||||
t.Errorf("User.Network == %q, want \"DummyIRC\" or \"DUMMY\"", network)
|
t.Fatalf("User.Network == %q, want \"DummyIRC\" or \"DUMMY\"", network)
|
||||||
}
|
}
|
||||||
|
|
||||||
if caseExample, ok := c.GetServerOption("NICKLEN"); !ok || caseExample != "20" {
|
if caseExample, ok := c.GetServerOption("NICKLEN"); !ok || caseExample != "20" {
|
||||||
t.Errorf("Client.GetServerOptions returned invalid ISUPPORT variable: %q", caseExample)
|
t.Fatalf("Client.GetServerOptions returned invalid ISUPPORT variable: %q", caseExample)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("getting user list")
|
|
||||||
users := c.UserList()
|
users := c.UserList()
|
||||||
t.Logf("getting channel list")
|
|
||||||
channels := c.ChannelList()
|
channels := c.ChannelList()
|
||||||
|
|
||||||
if !reflect.DeepEqual(users, []string{"fhjones", "nick2"}) {
|
if !reflect.DeepEqual(users, []string{"fhjones", "nick2"}) {
|
||||||
// This could fail too, if sorting isn't occurring.
|
// This could fail too, if sorting isn't occurring.
|
||||||
t.Errorf("got state users %#v, wanted: %#v", users, []string{"fhjones", "nick2"})
|
t.Fatalf("got state users %#v, wanted: %#v", users, []string{"fhjones", "nick2"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(channels, []string{"#channel", "#channel2"}) {
|
if !reflect.DeepEqual(channels, []string{"#channel", "#channel2"}) {
|
||||||
// This could fail too, if sorting isn't occurring.
|
// This could fail too, if sorting isn't occurring.
|
||||||
t.Errorf("got state channels %#v, wanted: %#v", channels, []string{"#channel", "#channel2"})
|
t.Fatalf("got state channels %#v, wanted: %#v", channels, []string{"#channel", "#channel2"})
|
||||||
}
|
}
|
||||||
|
|
||||||
fullChannels := c.Channels()
|
fullChannels := c.Channels()
|
||||||
for i := 0; i < len(fullChannels); i++ {
|
for i := 0; i < len(fullChannels); i++ {
|
||||||
if fullChannels[i].Name != channels[i] {
|
if fullChannels[i].Name != channels[i] {
|
||||||
t.Errorf("fullChannels name doesn't map to same name in ChannelsList: %q :: %#v", fullChannels[i].Name, channels)
|
t.Fatalf("fullChannels name doesn't map to same name in ChannelsList: %q :: %#v", fullChannels[i].Name, channels)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fullUsers := c.Users()
|
fullUsers := c.Users()
|
||||||
for i := 0; i < len(fullUsers); i++ {
|
for i := 0; i < len(fullUsers); i++ {
|
||||||
if fullUsers[i].Nick != users[i] {
|
if fullUsers[i].Nick != users[i] {
|
||||||
t.Errorf("fullUsers nick doesn't map to same nick in UsersList: %q :: %#v", fullUsers[i].Nick, users)
|
t.Fatalf("fullUsers nick doesn't map to same nick in UsersList: %q :: %#v", fullUsers[i].Nick, users)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := c.LookupChannel("#channel")
|
ch := c.LookupChannel("#channel")
|
||||||
if ch == nil {
|
if ch == nil {
|
||||||
t.Error("Client.LookupChannel returned nil on existing channel")
|
t.Fatal("Client.LookupChannel returned nil on existing channel")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
adm := ch.Admins(c)
|
adm := ch.Admins(c)
|
||||||
if adm == nil {
|
|
||||||
t.Errorf("admin list is nil")
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
admList := []string{}
|
admList := []string{}
|
||||||
for i := 0; i < len(adm); i++ {
|
for i := 0; i < len(adm); i++ {
|
||||||
admList = append(admList, adm[i].Nick)
|
admList = append(admList, adm[i].Nick)
|
||||||
@ -135,78 +127,76 @@ func TestState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(admList, []string{"nick2"}) {
|
if !reflect.DeepEqual(admList, []string{"nick2"}) {
|
||||||
t.Errorf("got Channel.Admins() == %#v, wanted %#v", admList, []string{"nick2"})
|
t.Fatalf("got Channel.Admins() == %#v, wanted %#v", admList, []string{"nick2"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(trustedList, []string{"nick2"}) {
|
if !reflect.DeepEqual(trustedList, []string{"nick2"}) {
|
||||||
t.Errorf("got Channel.Trusted() == %#v, wanted %#v", trustedList, []string{"nick2"})
|
t.Fatalf("got Channel.Trusted() == %#v, wanted %#v", trustedList, []string{"nick2"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if topic := ch.Topic; topic != "example topic" {
|
if topic := ch.Topic; topic != "example topic" {
|
||||||
t.Errorf("Channel.Topic == %q, want \"example topic\"", topic)
|
t.Fatalf("Channel.Topic == %q, want \"example topic\"", topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ch.Network != "DummyIRC" && ch.Network != "DUMMY" {
|
if ch.Network != "DummyIRC" && ch.Network != "DUMMY" {
|
||||||
t.Errorf("Channel.Network == %q, want \"DummyIRC\" or \"DUMMY\"", ch.Network)
|
t.Fatalf("Channel.Network == %q, want \"DummyIRC\" or \"DUMMY\"", ch.Network)
|
||||||
}
|
}
|
||||||
|
|
||||||
if in := ch.UserIn("fhjones"); !in {
|
if in := ch.UserIn("fhjones"); !in {
|
||||||
t.Errorf("Channel.UserIn == %t, want %t", in, true)
|
t.Fatalf("Channel.UserIn == %t, want %t", in, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if users := ch.Users(c); len(users) != 2 {
|
if users := ch.Users(c); len(users) != 2 {
|
||||||
t.Errorf("Channel.Users == %#v, wanted length of 2", users)
|
t.Fatalf("Channel.Users == %#v, wanted length of 2", users)
|
||||||
}
|
}
|
||||||
|
|
||||||
if h := c.GetHost(); h != "local.int" {
|
if h := c.GetHost(); h != "local.int" {
|
||||||
t.Errorf("Client.GetHost() == %q, want local.int", h)
|
t.Fatalf("Client.GetHost() == %q, want local.int", h)
|
||||||
}
|
}
|
||||||
|
|
||||||
if nick := c.GetNick(); nick != "fhjones" {
|
if nick := c.GetNick(); nick != "fhjones" {
|
||||||
t.Errorf("Client.GetNick() == %q, want nick", nick)
|
t.Fatalf("Client.GetNick() == %q, want nick", nick)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ident := c.GetIdent(); ident != "~user" {
|
if ident := c.GetIdent(); ident != "~user" {
|
||||||
t.Errorf("Client.GetIdent() == %q, want ~user", ident)
|
t.Fatalf("Client.GetIdent() == %q, want ~user", ident)
|
||||||
}
|
}
|
||||||
|
|
||||||
user := c.LookupUser("fhjones")
|
user := c.LookupUser("fhjones")
|
||||||
if user == nil {
|
if user == nil {
|
||||||
t.Errorf("Client.LookupUser() returned nil on existing user")
|
t.Fatal("Client.LookupUser() returned nil on existing user")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(user.ChannelList, []string{"#channel", "#channel2"}) {
|
if !reflect.DeepEqual(user.ChannelList, []string{"#channel", "#channel2"}) {
|
||||||
t.Errorf("User.ChannelList == %#v, wanted %#v", user.ChannelList, []string{"#channel", "#channel2"})
|
t.Fatalf("User.ChannelList == %#v, wanted %#v", user.ChannelList, []string{"#channel", "#channel2"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if count := len(user.Channels(c)); count != 2 {
|
if count := len(user.Channels(c)); count != 2 {
|
||||||
t.Errorf("len(User.Channels) == %d, want 2", count)
|
t.Fatalf("len(User.Channels) == %d, want 2", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Nick != "fhjones" {
|
if user.Nick != "fhjones" {
|
||||||
t.Errorf("User.Nick == %q, wanted \"nick\"", user.Nick)
|
t.Fatalf("User.Nick == %q, wanted \"nick\"", user.Nick)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Extras.Name != "realname" {
|
if user.Extras.Name != "realname" {
|
||||||
t.Errorf("User.Extras.Name == %q, wanted \"realname\"", user.Extras.Name)
|
t.Fatalf("User.Extras.Name == %q, wanted \"realname\"", user.Extras.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Host != "local.int" {
|
if user.Host != "local.int" {
|
||||||
t.Errorf("User.Host == %q, wanted \"local.int\"", user.Host)
|
t.Fatalf("User.Host == %q, wanted \"local.int\"", user.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Ident != "~user" {
|
if user.Ident != "~user" {
|
||||||
t.Errorf("User.Ident == %q, wanted \"~user\"", user.Ident)
|
t.Fatalf("User.Ident == %q, wanted \"~user\"", user.Ident)
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Network != "DummyIRC" && user.Network != "DUMMY" {
|
if user.Network != "DummyIRC" && user.Network != "DUMMY" {
|
||||||
t.Errorf("User.Network == %q, want \"DummyIRC\" or \"DUMMY\"", user.Network)
|
t.Fatalf("User.Network == %q, want \"DummyIRC\" or \"DUMMY\"", user.Network)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !user.InChannel("#channel2") {
|
if !user.InChannel("#channel2") {
|
||||||
t.Error("User.InChannel() returned false for existing channel")
|
t.Fatal("User.InChannel() returned false for existing channel")
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finishStart <- true
|
finishStart <- true
|
||||||
@ -217,11 +207,8 @@ func TestState(t *testing.T) {
|
|||||||
bounceStart <- true
|
bounceStart <- true
|
||||||
})
|
})
|
||||||
|
|
||||||
err := conn.SetDeadline(time.Now().Add(5 * time.Second))
|
conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||||
if err != nil {
|
_, err := conn.Write([]byte(mockConnStartState))
|
||||||
log.Fatalf(err.Error())
|
|
||||||
}
|
|
||||||
_, err = conn.Write([]byte(mockConnStartState))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -237,11 +224,11 @@ func TestState(t *testing.T) {
|
|||||||
finishEnd := make(chan bool, 1)
|
finishEnd := make(chan bool, 1)
|
||||||
go debounce(250*time.Millisecond, bounceEnd, func() {
|
go debounce(250*time.Millisecond, bounceEnd, func() {
|
||||||
if !reflect.DeepEqual(c.ChannelList(), []string{"#channel"}) {
|
if !reflect.DeepEqual(c.ChannelList(), []string{"#channel"}) {
|
||||||
t.Errorf("Client.ChannelList() == %#v, wanted %#v", c.ChannelList(), []string{"#channel"})
|
t.Fatalf("Client.ChannelList() == %#v, wanted %#v", c.ChannelList(), []string{"#channel"})
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(c.UserList(), []string{"notjones"}) {
|
if !reflect.DeepEqual(c.UserList(), []string{"notjones"}) {
|
||||||
t.Errorf("Client.UserList() == %#v, wanted %#v", c.UserList(), []string{"notjones"})
|
t.Fatalf("Client.UserList() == %#v, wanted %#v", c.UserList(), []string{"notjones"})
|
||||||
}
|
}
|
||||||
|
|
||||||
user := c.LookupUser("notjones")
|
user := c.LookupUser("notjones")
|
||||||
@ -250,19 +237,18 @@ func TestState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(user.ChannelList, []string{"#channel"}) {
|
if !reflect.DeepEqual(user.ChannelList, []string{"#channel"}) {
|
||||||
t.Errorf("user.ChannelList == %q, wanted %q", user.ChannelList, []string{"#channel"})
|
t.Fatalf("user.ChannelList == %q, wanted %q", user.ChannelList, []string{"#channel"})
|
||||||
}
|
}
|
||||||
|
|
||||||
channel := c.LookupChannel("#channel")
|
channel := c.LookupChannel("#channel")
|
||||||
if channel == nil {
|
if channel == nil {
|
||||||
t.Error("Client.LookupChannel() returned nil for existing channel")
|
t.Fatal("Client.LookupChannel() returned nil for existing channel")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(channel.UserList, []string{"notjones"}) {
|
if !reflect.DeepEqual(channel.UserList, []string{"notjones"}) {
|
||||||
t.Errorf("channel.UserList == %q, wanted %q", channel.UserList, []string{"notjones"})
|
t.Fatalf("channel.UserList == %q, wanted %q", channel.UserList, []string{"notjones"})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf(c.String())
|
|
||||||
finishEnd <- true
|
finishEnd <- true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user