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:
goinstall github.com/fluffle/goirc
go get github.com/fluffle/goirc/client
You can build the test client also with:
make
./gobot
This will connect to freenode and join `#go-nuts` by default, so be careful ;-)
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 ;-)
### Using the framework

View File

@ -3,20 +3,16 @@ package client
import (
"bufio"
"crypto/tls"
"github.com/fluffle/goevent/event"
"github.com/fluffle/golog/logging"
"github.com/fluffle/goirc/state"
"errors"
"fmt"
"github.com/fluffle/goevent/event"
"github.com/fluffle/goirc/state"
"github.com/fluffle/golog/logging"
"net"
"os"
"strings"
"time"
)
const (
second = int64(1e9)
)
// An IRC connection is represented by this struct.
type Conn struct {
// Connection Hostname and Nickname
@ -57,14 +53,12 @@ type Conn struct {
// Client->server ping frequency, in seconds. Defaults to 3m.
PingFreq int64
// Socket timeout, in seconds. Default to 5m.
Timeout int64
// Set this to true to disable flood protection and false to re-enable
Flood bool
// 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
@ -90,22 +84,21 @@ func Client(nick, ident, name string,
return nil
}
conn := &Conn{
ER: r,
ED: r,
l: l,
st: false,
in: make(chan *Line, 32),
out: make(chan string, 32),
cSend: make(chan bool),
cLoop: make(chan bool),
cPing: make(chan bool),
SSL: false,
SSLConfig: nil,
PingFreq: 180,
Timeout: 300,
Flood: false,
badness: 0,
lastsent: 0,
ER: r,
ED: r,
l: l,
st: false,
in: make(chan *Line, 32),
out: make(chan string, 32),
cSend: make(chan bool),
cLoop: make(chan bool),
cPing: make(chan bool),
SSL: false,
SSLConfig: nil,
PingFreq: 180,
Flood: false,
badness: 0,
lastsent: time.Now(),
}
conn.addIntHandlers()
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
// Connect(). The port will default to 6697 if ssl is enabled, and 6667
// 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 {
return os.NewError(fmt.Sprintf(
return errors.New(fmt.Sprintf(
"irc.Connect(): already connected to %s, cannot connect to %s",
conn.Host, host))
}
@ -196,7 +189,6 @@ func (conn *Conn) postConnect() {
conn.io = bufio.NewReadWriter(
bufio.NewReader(conn.sock),
bufio.NewWriter(conn.sock))
conn.sock.SetTimeout(conn.Timeout * second)
go conn.send()
go conn.recv()
if conn.PingFreq > 0 {
@ -231,7 +223,7 @@ func (conn *Conn) recv() {
for {
s, err := conn.io.ReadString('\n')
if err != nil {
conn.l.Error("irc.recv(): %s", err.String())
conn.l.Error("irc.recv(): %s", err.Error())
conn.shutdown()
return
}
@ -239,7 +231,7 @@ func (conn *Conn) recv() {
conn.l.Debug("<- %s", s)
if line := parseLine(s); line != nil {
line.Time = time.LocalTime()
line.Time = time.Now()
conn.in <- line
} else {
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.
func (conn *Conn) write(line string) {
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
conn.l.Debug("irc.rateLimit(): Flood! Sleeping for %.2f secs.",
float64(t)/float64(second))
t.Seconds())
<-time.After(t)
}
}
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()
return
}
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()
return
}
@ -300,19 +292,19 @@ func (conn *Conn) write(line string) {
}
// 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
// 1/120 of a second per character on that line.
linetime := 2*second + chars*second/120
elapsed := time.Nanoseconds() - conn.lastsent
linetime := 2*time.Second + time.Duration(chars)*time.Second/120
elapsed := time.Now().Sub(conn.lastsent)
if conn.badness += linetime - elapsed; conn.badness < 0 {
// 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
// calculation above, then we're at risk of "Excess Flood".
if conn.badness > 10*second {
if conn.badness > 10*time.Second {
return linetime
}
return 0

View File

@ -5,7 +5,7 @@ import (
"github.com/fluffle/goevent/event"
"github.com/fluffle/golog/logging"
"github.com/fluffle/goirc/state"
"gomock.googlecode.com/hg/gomock"
gomock "github.com/dsymonds/gomock/gomock"
"strings"
"testing"
"time"
@ -92,8 +92,12 @@ func TestClientAndStateTracking(t *testing.T) {
l := logging.NewMockLogger(ctrl)
st := state.NewMockStateTracker(ctrl)
for n, h := range intHandlers {
r.EXPECT().AddHandler(h, n)
for n, _ := range intHandlers {
// 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)
@ -110,8 +114,9 @@ func TestClientAndStateTracking(t *testing.T) {
}
// OK, while we're here with a mock event registry...
for n, h := range stHandlers {
r.EXPECT().AddHandler(h, n)
for n, _ := range stHandlers {
// See above.
ctrl.RecordCall(r, "AddHandler", gomock.Any(), []string{n})
}
c.EnableStateTracking()
@ -128,8 +133,9 @@ func TestClientAndStateTracking(t *testing.T) {
me := c.Me
c.ST = st
st.EXPECT().Wipe()
for n, h := range stHandlers {
r.EXPECT().DelHandler(h, n)
for n, _ := range stHandlers {
// See above.
ctrl.RecordCall(r, "DelHandler", gomock.Any(), []string{n})
}
c.DisableStateTracking()
if c.st || c.ST != nil || c.Me != me {
@ -412,8 +418,8 @@ func TestWrite(t *testing.T) {
s.nc.Expect("yo momma")
// Flood control is disabled -- setUp sets c.Flood = true -- so we should
// not have set c.badness or c.lastsent at this point.
if c.badness != 0 || c.lastsent != 0 {
// not have set c.badness at this point.
if c.badness != 0 {
t.Errorf("Flood control used when Flood = true.")
}
@ -421,8 +427,8 @@ func TestWrite(t *testing.T) {
c.write("she so useless")
s.nc.Expect("she so useless")
// The lastsent time should have been updated now.
if c.lastsent == 0 {
// The lastsent time should have been updated very recently...
if time.Now().Sub(c.lastsent) > time.Millisecond {
t.Errorf("Flood control not used when Flood = false.")
}
@ -449,24 +455,40 @@ func TestRateLimit(t *testing.T) {
c, s := setUp(t)
defer s.tearDown()
if c.badness != 0 || c.lastsent != 0 {
if c.badness != 0 {
t.Errorf("Bad initial values for rate limit variables.")
}
// badness will still be 0 because lastsent was 0 before rateLimit.
if l := c.rateLimit(60); l != 0 || c.badness != 0 || c.lastsent == 0 {
t.Errorf("Rate limit variables not updated correctly after rateLimit.")
// We'll be needing this later...
abs := func(i time.Duration) time.Duration {
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
// 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
// be minimal but it's guaranteed that it won't be zero. Use 1us as a fuzz.
// This seems to be the minimum timer resolution, on my laptop at least...
if l := c.rateLimit(60); l != 0 || c.badness - int64(25*1e8) > 1e3 {
// be minimal but it's guaranteed that it won't be zero. Use 10us as a fuzz.
if l := c.rateLimit(60); l != 0 || abs(c.badness - 25*1e8) > 10 * time.Microsecond {
t.Errorf("Rate limit calculating badness incorrectly.")
}
// 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("l=%d, badness=%d", l, c.badness)
}
}

View File

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

View File

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

View File

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

View File

@ -11,9 +11,9 @@ import (
type Channel struct {
Name, Topic string
Modes *ChanMode
lookup map[string]*Nick
lookup map[string]*Nick
nicks map[*Nick]*ChanPrivs
l logging.Logger
l logging.Logger
}
// A struct representing the modes of an IRC Channel
@ -93,11 +93,11 @@ func init() {
func NewChannel(name string, l logging.Logger) *Channel {
return &Channel{
Name: name,
Modes: new(ChanMode),
nicks: make(map[*Nick]*ChanPrivs),
Name: name,
Modes: new(ChanMode),
nicks: make(map[*Nick]*ChanPrivs),
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.
func (ch *Channel) delNick(nk *Nick) {
if _, ok := ch.nicks[nk]; ok {
ch.nicks[nk] = nil, false
ch.lookup[nk.Nick] = nil, false
delete(ch.nicks, nk)
delete(ch.lookup, nk.Nick)
} else {
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':
if len(modeargs) != 0 {
if nk, ok := ch.lookup[modeargs[0]]; ok {
if nk, ok := ch.lookup[modeargs[0]]; ok {
cp := ch.nicks[nk]
switch m {
case 'q':

View File

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

View File

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

View File

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