Merge branch 'release'

Conflicts:
	client/connection.go
	client/connection_test.go
This commit is contained in:
Alex Bramley 2012-06-06 16:25:07 +01:00
commit 9c67c42fa1
11 changed files with 175 additions and 165 deletions

View File

@ -5,14 +5,9 @@ GoIRC Client Framework
Pretty simple, really: Pretty simple, really:
goinstall github.com/fluffle/goirc go get github.com/fluffle/goirc/client
You can build the test client also with: There is some example code that demonstrates usage of the library in `client.go`. This will connect to freenode and join `#go-nuts` by default, so be careful ;-)
make
./gobot
This will connect to freenode and join `#go-nuts` by default, so be careful ;-)
### Using the framework ### Using the framework

View File

@ -3,20 +3,16 @@ package client
import ( import (
"bufio" "bufio"
"crypto/tls" "crypto/tls"
"github.com/fluffle/goevent/event" "errors"
"github.com/fluffle/golog/logging"
"github.com/fluffle/goirc/state"
"fmt" "fmt"
"github.com/fluffle/goevent/event"
"github.com/fluffle/goirc/state"
"github.com/fluffle/golog/logging"
"net" "net"
"os"
"strings" "strings"
"time" "time"
) )
const (
second = int64(1e9)
)
// An IRC connection is represented by this struct. // An IRC connection is represented by this struct.
type Conn struct { type Conn struct {
// Connection Hostname and Nickname // Connection Hostname and Nickname
@ -57,14 +53,12 @@ type Conn struct {
// Client->server ping frequency, in seconds. Defaults to 3m. // Client->server ping frequency, in seconds. Defaults to 3m.
PingFreq int64 PingFreq int64
// Socket timeout, in seconds. Default to 5m.
Timeout int64
// Set this to true to disable flood protection and false to re-enable // Set this to true to disable flood protection and false to re-enable
Flood bool Flood bool
// Internal counters for flood protection // Internal counters for flood protection
badness, lastsent int64 badness time.Duration
lastsent time.Time
} }
// Creates a new IRC connection object, but doesn't connect to anything so // Creates a new IRC connection object, but doesn't connect to anything so
@ -90,22 +84,21 @@ func Client(nick, ident, name string,
return nil return nil
} }
conn := &Conn{ conn := &Conn{
ER: r, ER: r,
ED: r, ED: r,
l: l, l: l,
st: false, st: false,
in: make(chan *Line, 32), in: make(chan *Line, 32),
out: make(chan string, 32), out: make(chan string, 32),
cSend: make(chan bool), cSend: make(chan bool),
cLoop: make(chan bool), cLoop: make(chan bool),
cPing: make(chan bool), cPing: make(chan bool),
SSL: false, SSL: false,
SSLConfig: nil, SSLConfig: nil,
PingFreq: 180, PingFreq: 180,
Timeout: 300, Flood: false,
Flood: false, badness: 0,
badness: 0, lastsent: time.Now(),
lastsent: 0,
} }
conn.addIntHandlers() conn.addIntHandlers()
conn.Me = state.NewNick(nick, l) conn.Me = state.NewNick(nick, l)
@ -151,9 +144,9 @@ func (conn *Conn) initialise() {
// on the connection to the IRC server, set Conn.SSL to true before calling // on the connection to the IRC server, set Conn.SSL to true before calling
// Connect(). The port will default to 6697 if ssl is enabled, and 6667 // Connect(). The port will default to 6697 if ssl is enabled, and 6667
// otherwise. You can also provide an optional connect password. // otherwise. You can also provide an optional connect password.
func (conn *Conn) Connect(host string, pass ...string) os.Error { func (conn *Conn) Connect(host string, pass ...string) error {
if conn.Connected { if conn.Connected {
return os.NewError(fmt.Sprintf( return errors.New(fmt.Sprintf(
"irc.Connect(): already connected to %s, cannot connect to %s", "irc.Connect(): already connected to %s, cannot connect to %s",
conn.Host, host)) conn.Host, host))
} }
@ -196,7 +189,6 @@ func (conn *Conn) postConnect() {
conn.io = bufio.NewReadWriter( conn.io = bufio.NewReadWriter(
bufio.NewReader(conn.sock), bufio.NewReader(conn.sock),
bufio.NewWriter(conn.sock)) bufio.NewWriter(conn.sock))
conn.sock.SetTimeout(conn.Timeout * second)
go conn.send() go conn.send()
go conn.recv() go conn.recv()
if conn.PingFreq > 0 { if conn.PingFreq > 0 {
@ -231,7 +223,7 @@ func (conn *Conn) recv() {
for { for {
s, err := conn.io.ReadString('\n') s, err := conn.io.ReadString('\n')
if err != nil { if err != nil {
conn.l.Error("irc.recv(): %s", err.String()) conn.l.Error("irc.recv(): %s", err.Error())
conn.shutdown() conn.shutdown()
return return
} }
@ -239,7 +231,7 @@ func (conn *Conn) recv() {
conn.l.Debug("<- %s", s) conn.l.Debug("<- %s", s)
if line := parseLine(s); line != nil { if line := parseLine(s); line != nil {
line.Time = time.LocalTime() line.Time = time.Now()
conn.in <- line conn.in <- line
} else { } else {
conn.l.Warn("irc.recv(): problems parsing line:\n %s", s) conn.l.Warn("irc.recv(): problems parsing line:\n %s", s)
@ -278,21 +270,21 @@ func (conn *Conn) runLoop() {
// using Hybrid's algorithm to rate limit if conn.Flood is false. // using Hybrid's algorithm to rate limit if conn.Flood is false.
func (conn *Conn) write(line string) { func (conn *Conn) write(line string) {
if !conn.Flood { if !conn.Flood {
if t := conn.rateLimit(int64(len(line))); t != 0 { if t := conn.rateLimit(len(line)); t != 0 {
// sleep for the current line's time value before sending it // sleep for the current line's time value before sending it
conn.l.Debug("irc.rateLimit(): Flood! Sleeping for %.2f secs.", conn.l.Debug("irc.rateLimit(): Flood! Sleeping for %.2f secs.",
float64(t)/float64(second)) t.Seconds())
<-time.After(t) <-time.After(t)
} }
} }
if _, err := conn.io.WriteString(line + "\r\n"); err != nil { if _, err := conn.io.WriteString(line + "\r\n"); err != nil {
conn.l.Error("irc.send(): %s", err.String()) conn.l.Error("irc.send(): %s", err.Error())
conn.shutdown() conn.shutdown()
return return
} }
if err := conn.io.Flush(); err != nil { if err := conn.io.Flush(); err != nil {
conn.l.Error("irc.send(): %s", err.String()) conn.l.Error("irc.send(): %s", err.Error())
conn.shutdown() conn.shutdown()
return return
} }
@ -300,19 +292,19 @@ func (conn *Conn) write(line string) {
} }
// Implement Hybrid's flood control algorithm to rate-limit outgoing lines. // Implement Hybrid's flood control algorithm to rate-limit outgoing lines.
func (conn *Conn) rateLimit(chars int64) int64 { func (conn *Conn) rateLimit(chars int) time.Duration {
// Hybrid's algorithm allows for 2 seconds per line and an additional // Hybrid's algorithm allows for 2 seconds per line and an additional
// 1/120 of a second per character on that line. // 1/120 of a second per character on that line.
linetime := 2*second + chars*second/120 linetime := 2*time.Second + time.Duration(chars)*time.Second/120
elapsed := time.Nanoseconds() - conn.lastsent elapsed := time.Now().Sub(conn.lastsent)
if conn.badness += linetime - elapsed; conn.badness < 0 { if conn.badness += linetime - elapsed; conn.badness < 0 {
// negative badness times are badness... // negative badness times are badness...
conn.badness = int64(0) conn.badness = 0
} }
conn.lastsent = time.Nanoseconds() conn.lastsent = time.Now()
// If we've sent more than 10 second's worth of lines according to the // If we've sent more than 10 second's worth of lines according to the
// calculation above, then we're at risk of "Excess Flood". // calculation above, then we're at risk of "Excess Flood".
if conn.badness > 10*second { if conn.badness > 10*time.Second {
return linetime return linetime
} }
return 0 return 0

View File

@ -5,7 +5,7 @@ import (
"github.com/fluffle/goevent/event" "github.com/fluffle/goevent/event"
"github.com/fluffle/golog/logging" "github.com/fluffle/golog/logging"
"github.com/fluffle/goirc/state" "github.com/fluffle/goirc/state"
"gomock.googlecode.com/hg/gomock" gomock "github.com/dsymonds/gomock/gomock"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -92,8 +92,12 @@ func TestClientAndStateTracking(t *testing.T) {
l := logging.NewMockLogger(ctrl) l := logging.NewMockLogger(ctrl)
st := state.NewMockStateTracker(ctrl) st := state.NewMockStateTracker(ctrl)
for n, h := range intHandlers { for n, _ := range intHandlers {
r.EXPECT().AddHandler(h, n) // We can't use EXPECT() here as comparisons of functions are
// no longer valid in Go, which causes reflect.DeepEqual to bail.
// Instead, ignore the function arg and just ensure that all the
// handler names are correctly passed to AddHandler.
ctrl.RecordCall(r, "AddHandler", gomock.Any(), []string{n})
} }
c := Client("test", "test", "Testing IRC", r, l) c := Client("test", "test", "Testing IRC", r, l)
@ -110,8 +114,9 @@ func TestClientAndStateTracking(t *testing.T) {
} }
// OK, while we're here with a mock event registry... // OK, while we're here with a mock event registry...
for n, h := range stHandlers { for n, _ := range stHandlers {
r.EXPECT().AddHandler(h, n) // See above.
ctrl.RecordCall(r, "AddHandler", gomock.Any(), []string{n})
} }
c.EnableStateTracking() c.EnableStateTracking()
@ -128,8 +133,9 @@ func TestClientAndStateTracking(t *testing.T) {
me := c.Me me := c.Me
c.ST = st c.ST = st
st.EXPECT().Wipe() st.EXPECT().Wipe()
for n, h := range stHandlers { for n, _ := range stHandlers {
r.EXPECT().DelHandler(h, n) // See above.
ctrl.RecordCall(r, "DelHandler", gomock.Any(), []string{n})
} }
c.DisableStateTracking() c.DisableStateTracking()
if c.st || c.ST != nil || c.Me != me { if c.st || c.ST != nil || c.Me != me {
@ -412,8 +418,8 @@ func TestWrite(t *testing.T) {
s.nc.Expect("yo momma") s.nc.Expect("yo momma")
// Flood control is disabled -- setUp sets c.Flood = true -- so we should // Flood control is disabled -- setUp sets c.Flood = true -- so we should
// not have set c.badness or c.lastsent at this point. // not have set c.badness at this point.
if c.badness != 0 || c.lastsent != 0 { if c.badness != 0 {
t.Errorf("Flood control used when Flood = true.") t.Errorf("Flood control used when Flood = true.")
} }
@ -421,8 +427,8 @@ func TestWrite(t *testing.T) {
c.write("she so useless") c.write("she so useless")
s.nc.Expect("she so useless") s.nc.Expect("she so useless")
// The lastsent time should have been updated now. // The lastsent time should have been updated very recently...
if c.lastsent == 0 { if time.Now().Sub(c.lastsent) > time.Millisecond {
t.Errorf("Flood control not used when Flood = false.") t.Errorf("Flood control not used when Flood = false.")
} }
@ -449,24 +455,40 @@ func TestRateLimit(t *testing.T) {
c, s := setUp(t) c, s := setUp(t)
defer s.tearDown() defer s.tearDown()
if c.badness != 0 || c.lastsent != 0 { if c.badness != 0 {
t.Errorf("Bad initial values for rate limit variables.") t.Errorf("Bad initial values for rate limit variables.")
} }
// badness will still be 0 because lastsent was 0 before rateLimit. // We'll be needing this later...
if l := c.rateLimit(60); l != 0 || c.badness != 0 || c.lastsent == 0 { abs := func(i time.Duration) time.Duration {
t.Errorf("Rate limit variables not updated correctly after rateLimit.") if (i < 0) {
return -i
}
return i
} }
// Since the changes to the time module, c.lastsent is now a time.Time.
// It's initialised on client creation to time.Now() which for the purposes
// of this test was probably around 1.2 ms ago. This is inconvenient.
// Making it >10s ago effectively clears out the inconsistency, as this
// makes elapsed > linetime and thus zeros c.badness and resets c.lastsent.
c.lastsent = time.Now().Add(-10 * time.Second)
if l := c.rateLimit(60); l != 0 || c.badness != 0 {
t.Errorf("Rate limit got non-zero badness from long-ago lastsent.")
}
// So, time at the nanosecond resolution is a bit of a bitch. Choosing 60 // So, time at the nanosecond resolution is a bit of a bitch. Choosing 60
// characters as the line length means we should be increasing badness by // characters as the line length means we should be increasing badness by
// 2.5 seconds minus the delta between the two ratelimit calls. This should // 2.5 seconds minus the delta between the two ratelimit calls. This should
// be minimal but it's guaranteed that it won't be zero. Use 1us as a fuzz. // be minimal but it's guaranteed that it won't be zero. Use 10us as a fuzz.
// This seems to be the minimum timer resolution, on my laptop at least... if l := c.rateLimit(60); l != 0 || abs(c.badness - 25*1e8) > 10 * time.Microsecond {
if l := c.rateLimit(60); l != 0 || c.badness - int64(25*1e8) > 1e3 {
t.Errorf("Rate limit calculating badness incorrectly.") t.Errorf("Rate limit calculating badness incorrectly.")
} }
// At this point, we can tip over the badness scale, with a bit of help. // At this point, we can tip over the badness scale, with a bit of help.
if l := c.rateLimit(360); l == 80*1e8 || c.badness - int64(105*1e8) > 1e3 { // 720 chars => +8 seconds of badness => 10.5 seconds => ratelimit
if l := c.rateLimit(720); l != 8 * time.Second ||
abs(c.badness - 105*1e8) > 10 * time.Microsecond {
t.Errorf("Rate limit failed to return correct limiting values.") t.Errorf("Rate limit failed to return correct limiting values.")
t.Errorf("l=%d, badness=%d", l, c.badness)
} }
} }

View File

@ -2,7 +2,7 @@ package client
import ( import (
"github.com/fluffle/goirc/state" "github.com/fluffle/goirc/state"
"gomock.googlecode.com/hg/gomock" gomock "github.com/dsymonds/gomock/gomock"
"testing" "testing"
) )

View File

@ -14,7 +14,7 @@ type Line struct {
Nick, Ident, Host, Src string Nick, Ident, Host, Src string
Cmd, Raw string Cmd, Raw string
Args []string Args []string
Time *time.Time Time time.Time
} }
// NOTE: this doesn't copy l.Time (this should be read-only anyway) // NOTE: this doesn't copy l.Time (this should be read-only anyway)

View File

@ -1,6 +1,7 @@
package client package client
import ( import (
"io"
"net" "net"
"os" "os"
"strings" "strings"
@ -23,7 +24,7 @@ type mockNetConn struct {
rc chan bool rc chan bool
closed bool closed bool
rt, wt int64 rt, wt time.Time
} }
func MockNetConn(t *testing.T) *mockNetConn { func MockNetConn(t *testing.T) *mockNetConn {
@ -96,9 +97,9 @@ func (m *mockNetConn) ExpectNothing() {
} }
// Implement net.Conn interface // Implement net.Conn interface
func (m *mockNetConn) Read(b []byte) (int, os.Error) { func (m *mockNetConn) Read(b []byte) (int, error) {
if m.closed { if m.closed {
return 0, os.EINVAL return 0, os.ErrInvalid
} }
l := 0 l := 0
select { select {
@ -106,14 +107,14 @@ func (m *mockNetConn) Read(b []byte) (int, os.Error) {
l = len(s) l = len(s)
copy(b, s) copy(b, s)
case <-m.closers[mockReadCloser]: case <-m.closers[mockReadCloser]:
return 0, os.EOF return 0, io.EOF
} }
return l, nil return l, nil
} }
func (m *mockNetConn) Write(s []byte) (int, os.Error) { func (m *mockNetConn) Write(s []byte) (int, error) {
if m.closed { if m.closed {
return 0, os.EINVAL return 0, os.ErrInvalid
} }
b := make([]byte, len(s)) b := make([]byte, len(s))
copy(b, s) copy(b, s)
@ -121,9 +122,9 @@ func (m *mockNetConn) Write(s []byte) (int, os.Error) {
return len(s), nil return len(s), nil
} }
func (m *mockNetConn) Close() os.Error { func (m *mockNetConn) Close() error {
if m.closed { if m.closed {
return os.EINVAL return os.ErrInvalid
} }
// Shut down *ALL* the goroutines! // Shut down *ALL* the goroutines!
// This will trigger an EOF event in Read() too // This will trigger an EOF event in Read() too
@ -142,18 +143,18 @@ func (m *mockNetConn) RemoteAddr() net.Addr {
return &net.IPAddr{net.IPv4(127, 0, 0, 1)} return &net.IPAddr{net.IPv4(127, 0, 0, 1)}
} }
func (m *mockNetConn) SetTimeout(ns int64) os.Error { func (m *mockNetConn) SetDeadline(t time.Time) error {
m.rt = ns m.rt = t
m.wt = ns m.wt = t
return nil return nil
} }
func (m *mockNetConn) SetReadTimeout(ns int64) os.Error { func (m *mockNetConn) SetReadDeadline(t time.Time) error {
m.rt = ns m.rt = t
return nil return nil
} }
func (m *mockNetConn) SetWriteTimeout(ns int64) os.Error { func (m *mockNetConn) SetWriteDeadline(t time.Time) error {
m.wt = ns m.wt = t
return nil return nil
} }

View File

@ -11,9 +11,9 @@ import (
type Channel struct { type Channel struct {
Name, Topic string Name, Topic string
Modes *ChanMode Modes *ChanMode
lookup map[string]*Nick lookup map[string]*Nick
nicks map[*Nick]*ChanPrivs nicks map[*Nick]*ChanPrivs
l logging.Logger l logging.Logger
} }
// A struct representing the modes of an IRC Channel // A struct representing the modes of an IRC Channel
@ -93,11 +93,11 @@ func init() {
func NewChannel(name string, l logging.Logger) *Channel { func NewChannel(name string, l logging.Logger) *Channel {
return &Channel{ return &Channel{
Name: name, Name: name,
Modes: new(ChanMode), Modes: new(ChanMode),
nicks: make(map[*Nick]*ChanPrivs), nicks: make(map[*Nick]*ChanPrivs),
lookup: make(map[string]*Nick), lookup: make(map[string]*Nick),
l: l, l: l,
} }
} }
@ -125,8 +125,8 @@ func (ch *Channel) addNick(nk *Nick, cp *ChanPrivs) {
// Disassociates a Nick from a Channel. // Disassociates a Nick from a Channel.
func (ch *Channel) delNick(nk *Nick) { func (ch *Channel) delNick(nk *Nick) {
if _, ok := ch.nicks[nk]; ok { if _, ok := ch.nicks[nk]; ok {
ch.nicks[nk] = nil, false delete(ch.nicks, nk)
ch.lookup[nk.Nick] = nil, false delete(ch.lookup, nk.Nick)
} else { } else {
ch.l.Warn("Channel.delNick(): %s not on %s.", nk.Nick, ch.Name) ch.l.Warn("Channel.delNick(): %s not on %s.", nk.Nick, ch.Name)
} }
@ -181,7 +181,7 @@ func (ch *Channel) ParseModes(modes string, modeargs ...string) {
} }
case 'q', 'a', 'o', 'h', 'v': case 'q', 'a', 'o', 'h', 'v':
if len(modeargs) != 0 { if len(modeargs) != 0 {
if nk, ok := ch.lookup[modeargs[0]]; ok { if nk, ok := ch.lookup[modeargs[0]]; ok {
cp := ch.nicks[nk] cp := ch.nicks[nk]
switch m { switch m {
case 'q': case 'q':

View File

@ -4,7 +4,7 @@
package state package state
import ( import (
gomock "gomock.googlecode.com/hg/gomock" gomock "github.com/dsymonds/gomock/gomock"
) )
// Mock of StateTracker interface // Mock of StateTracker interface
@ -24,127 +24,127 @@ func NewMockStateTracker(ctrl *gomock.Controller) *MockStateTracker {
return mock return mock
} }
func (m *MockStateTracker) EXPECT() *_MockStateTrackerRecorder { func (_m *MockStateTracker) EXPECT() *_MockStateTrackerRecorder {
return m.recorder return _m.recorder
} }
func (m *MockStateTracker) NewNick(nick string) *Nick { func (_m *MockStateTracker) NewNick(nick string) *Nick {
ret := m.ctrl.Call(m, "NewNick", nick) ret := _m.ctrl.Call(_m, "NewNick", nick)
ret0, _ := ret[0].(*Nick) ret0, _ := ret[0].(*Nick)
return ret0 return ret0
} }
func (mr *_MockStateTrackerRecorder) NewNick(arg0 interface{}) *gomock.Call { func (_mr *_MockStateTrackerRecorder) NewNick(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "NewNick", arg0) return _mr.mock.ctrl.RecordCall(_mr.mock, "NewNick", arg0)
} }
func (m *MockStateTracker) GetNick(nick string) *Nick { func (_m *MockStateTracker) GetNick(nick string) *Nick {
ret := m.ctrl.Call(m, "GetNick", nick) ret := _m.ctrl.Call(_m, "GetNick", nick)
ret0, _ := ret[0].(*Nick) ret0, _ := ret[0].(*Nick)
return ret0 return ret0
} }
func (mr *_MockStateTrackerRecorder) GetNick(arg0 interface{}) *gomock.Call { func (_mr *_MockStateTrackerRecorder) GetNick(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "GetNick", arg0) return _mr.mock.ctrl.RecordCall(_mr.mock, "GetNick", arg0)
} }
func (m *MockStateTracker) ReNick(old string, neu string) { func (_m *MockStateTracker) ReNick(old string, neu string) {
m.ctrl.Call(m, "ReNick", old, neu) _m.ctrl.Call(_m, "ReNick", old, neu)
} }
func (mr *_MockStateTrackerRecorder) ReNick(arg0, arg1 interface{}) *gomock.Call { func (_mr *_MockStateTrackerRecorder) ReNick(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "ReNick", arg0, arg1) return _mr.mock.ctrl.RecordCall(_mr.mock, "ReNick", arg0, arg1)
} }
func (m *MockStateTracker) DelNick(nick string) { func (_m *MockStateTracker) DelNick(nick string) {
m.ctrl.Call(m, "DelNick", nick) _m.ctrl.Call(_m, "DelNick", nick)
} }
func (mr *_MockStateTrackerRecorder) DelNick(arg0 interface{}) *gomock.Call { func (_mr *_MockStateTrackerRecorder) DelNick(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "DelNick", arg0) return _mr.mock.ctrl.RecordCall(_mr.mock, "DelNick", arg0)
} }
func (m *MockStateTracker) NewChannel(channel string) *Channel { func (_m *MockStateTracker) NewChannel(channel string) *Channel {
ret := m.ctrl.Call(m, "NewChannel", channel) ret := _m.ctrl.Call(_m, "NewChannel", channel)
ret0, _ := ret[0].(*Channel) ret0, _ := ret[0].(*Channel)
return ret0 return ret0
} }
func (mr *_MockStateTrackerRecorder) NewChannel(arg0 interface{}) *gomock.Call { func (_mr *_MockStateTrackerRecorder) NewChannel(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "NewChannel", arg0) return _mr.mock.ctrl.RecordCall(_mr.mock, "NewChannel", arg0)
} }
func (m *MockStateTracker) GetChannel(channel string) *Channel { func (_m *MockStateTracker) GetChannel(channel string) *Channel {
ret := m.ctrl.Call(m, "GetChannel", channel) ret := _m.ctrl.Call(_m, "GetChannel", channel)
ret0, _ := ret[0].(*Channel) ret0, _ := ret[0].(*Channel)
return ret0 return ret0
} }
func (mr *_MockStateTrackerRecorder) GetChannel(arg0 interface{}) *gomock.Call { func (_mr *_MockStateTrackerRecorder) GetChannel(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "GetChannel", arg0) return _mr.mock.ctrl.RecordCall(_mr.mock, "GetChannel", arg0)
} }
func (m *MockStateTracker) DelChannel(channel string) { func (_m *MockStateTracker) DelChannel(channel string) {
m.ctrl.Call(m, "DelChannel", channel) _m.ctrl.Call(_m, "DelChannel", channel)
} }
func (mr *_MockStateTrackerRecorder) DelChannel(arg0 interface{}) *gomock.Call { func (_mr *_MockStateTrackerRecorder) DelChannel(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "DelChannel", arg0) return _mr.mock.ctrl.RecordCall(_mr.mock, "DelChannel", arg0)
} }
func (m *MockStateTracker) Me() *Nick { func (_m *MockStateTracker) Me() *Nick {
ret := m.ctrl.Call(m, "Me") ret := _m.ctrl.Call(_m, "Me")
ret0, _ := ret[0].(*Nick) ret0, _ := ret[0].(*Nick)
return ret0 return ret0
} }
func (mr *_MockStateTrackerRecorder) Me() *gomock.Call { func (_mr *_MockStateTrackerRecorder) Me() *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "Me") return _mr.mock.ctrl.RecordCall(_mr.mock, "Me")
} }
func (m *MockStateTracker) IsOn(channel string, nick string) (*ChanPrivs, bool) { func (_m *MockStateTracker) IsOn(channel string, nick string) (*ChanPrivs, bool) {
ret := m.ctrl.Call(m, "IsOn", channel, nick) ret := _m.ctrl.Call(_m, "IsOn", channel, nick)
ret0, _ := ret[0].(*ChanPrivs) ret0, _ := ret[0].(*ChanPrivs)
ret1, _ := ret[1].(bool) ret1, _ := ret[1].(bool)
return ret0, ret1 return ret0, ret1
} }
func (mr *_MockStateTrackerRecorder) IsOn(arg0, arg1 interface{}) *gomock.Call { func (_mr *_MockStateTrackerRecorder) IsOn(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "IsOn", arg0, arg1) return _mr.mock.ctrl.RecordCall(_mr.mock, "IsOn", arg0, arg1)
} }
func (m *MockStateTracker) Associate(channel *Channel, nick *Nick) *ChanPrivs { func (_m *MockStateTracker) Associate(channel *Channel, nick *Nick) *ChanPrivs {
ret := m.ctrl.Call(m, "Associate", channel, nick) ret := _m.ctrl.Call(_m, "Associate", channel, nick)
ret0, _ := ret[0].(*ChanPrivs) ret0, _ := ret[0].(*ChanPrivs)
return ret0 return ret0
} }
func (mr *_MockStateTrackerRecorder) Associate(arg0, arg1 interface{}) *gomock.Call { func (_mr *_MockStateTrackerRecorder) Associate(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "Associate", arg0, arg1) return _mr.mock.ctrl.RecordCall(_mr.mock, "Associate", arg0, arg1)
} }
func (m *MockStateTracker) Dissociate(channel *Channel, nick *Nick) { func (_m *MockStateTracker) Dissociate(channel *Channel, nick *Nick) {
m.ctrl.Call(m, "Dissociate", channel, nick) _m.ctrl.Call(_m, "Dissociate", channel, nick)
} }
func (mr *_MockStateTrackerRecorder) Dissociate(arg0, arg1 interface{}) *gomock.Call { func (_mr *_MockStateTrackerRecorder) Dissociate(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "Dissociate", arg0, arg1) return _mr.mock.ctrl.RecordCall(_mr.mock, "Dissociate", arg0, arg1)
} }
func (m *MockStateTracker) Wipe() { func (_m *MockStateTracker) Wipe() {
m.ctrl.Call(m, "Wipe") _m.ctrl.Call(_m, "Wipe")
} }
func (mr *_MockStateTrackerRecorder) Wipe() *gomock.Call { func (_mr *_MockStateTrackerRecorder) Wipe() *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "Wipe") return _mr.mock.ctrl.RecordCall(_mr.mock, "Wipe")
} }
func (m *MockStateTracker) String() string { func (_m *MockStateTracker) String() string {
ret := m.ctrl.Call(m, "String") ret := _m.ctrl.Call(_m, "String")
ret0, _ := ret[0].(string) ret0, _ := ret[0].(string)
return ret0 return ret0
} }
func (mr *_MockStateTrackerRecorder) String() *gomock.Call { func (_mr *_MockStateTrackerRecorder) String() *gomock.Call {
return mr.mock.ctrl.RecordCall(mr.mock, "String") return _mr.mock.ctrl.RecordCall(_mr.mock, "String")
} }

View File

@ -9,9 +9,9 @@ import (
type Nick struct { type Nick struct {
Nick, Ident, Host, Name string Nick, Ident, Host, Name string
Modes *NickMode Modes *NickMode
lookup map[string]*Channel lookup map[string]*Channel
chans map[*Channel]*ChanPrivs chans map[*Channel]*ChanPrivs
l logging.Logger l logging.Logger
} }
// A struct representing the modes of an IRC Nick (User Modes) // A struct representing the modes of an IRC Nick (User Modes)
@ -46,11 +46,11 @@ func init() {
func NewNick(n string, l logging.Logger) *Nick { func NewNick(n string, l logging.Logger) *Nick {
return &Nick{ return &Nick{
Nick: n, Nick: n,
Modes: new(NickMode), Modes: new(NickMode),
chans: make(map[*Channel]*ChanPrivs), chans: make(map[*Channel]*ChanPrivs),
lookup: make(map[string]*Channel), lookup: make(map[string]*Channel),
l: l, l: l,
} }
} }
@ -78,8 +78,8 @@ func (nk *Nick) addChannel(ch *Channel, cp *ChanPrivs) {
// Disassociates a Channel from a Nick. // Disassociates a Channel from a Nick.
func (nk *Nick) delChannel(ch *Channel) { func (nk *Nick) delChannel(ch *Channel) {
if _, ok := nk.chans[ch]; ok { if _, ok := nk.chans[ch]; ok {
nk.chans[ch] = nil, false delete(nk.chans, ch)
nk.lookup[ch.Name] = nil, false delete(nk.lookup, ch.Name)
} else { } else {
nk.l.Warn("Nick.delChannel(): %s not on %s.", nk.Nick, ch.Name) nk.l.Warn("Nick.delChannel(): %s not on %s.", nk.Nick, ch.Name)
} }

View File

@ -45,7 +45,7 @@ func NewTracker(mynick string, l logging.Logger) *stateTracker {
st := &stateTracker{ st := &stateTracker{
chans: make(map[string]*Channel), chans: make(map[string]*Channel),
nicks: make(map[string]*Nick), nicks: make(map[string]*Nick),
l: l, l: l,
} }
st.me = st.NewNick(mynick) st.me = st.NewNick(mynick)
return st return st
@ -88,12 +88,12 @@ func (st *stateTracker) ReNick(old, neu string) {
if nk, ok := st.nicks[old]; ok { if nk, ok := st.nicks[old]; ok {
if _, ok := st.nicks[neu]; !ok { if _, ok := st.nicks[neu]; !ok {
nk.Nick = neu nk.Nick = neu
st.nicks[old] = nil, false delete(st.nicks, old)
st.nicks[neu] = nk st.nicks[neu] = nk
for ch, _ := range nk.chans { for ch, _ := range nk.chans {
// We also need to update the lookup maps of all the channels // We also need to update the lookup maps of all the channels
// the nick is on, to keep things in sync. // the nick is on, to keep things in sync.
ch.lookup[old] = nil, false delete(ch.lookup, old)
ch.lookup[neu] = nk ch.lookup[neu] = nk
} }
} else { } else {
@ -123,7 +123,7 @@ func (st *stateTracker) delNick(nk *Nick) {
st.l.Error("StateTracker.DelNick(): TRYING TO DELETE ME :-(") st.l.Error("StateTracker.DelNick(): TRYING TO DELETE ME :-(")
return return
} }
st.nicks[nk.Nick] = nil, false delete(st.nicks, nk.Nick)
for ch, _ := range nk.chans { for ch, _ := range nk.chans {
nk.delChannel(ch) nk.delChannel(ch)
ch.delNick(nk) ch.delNick(nk)
@ -165,7 +165,7 @@ func (st *stateTracker) DelChannel(c string) {
} }
func (st *stateTracker) delChannel(ch *Channel) { func (st *stateTracker) delChannel(ch *Channel) {
st.chans[ch.Name] = nil, false delete(st.chans, ch.Name)
for nk, _ := range ch.nicks { for nk, _ := range ch.nicks {
ch.delNick(nk) ch.delNick(nk)
nk.delChannel(ch) nk.delChannel(ch)

View File

@ -2,7 +2,7 @@ package state
import ( import (
"github.com/fluffle/golog/logging" "github.com/fluffle/golog/logging"
"gomock.googlecode.com/hg/gomock" gomock "github.com/dsymonds/gomock/gomock"
"testing" "testing"
) )