From b1242aa351cb09654cd326421263993beee1d30d Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Sat, 16 Feb 2013 18:05:56 +0000 Subject: [PATCH] Break out tweakable things into a Config struct. --- client/connection.go | 90 +++++++++++++++++++++++++-------------- client/connection_test.go | 12 +++--- client/handlers.go | 6 +-- client/handlers_test.go | 8 ++-- 4 files changed, 71 insertions(+), 45 deletions(-) diff --git a/client/connection.go b/client/connection.go index 4fda637..7d18bec 100644 --- a/client/connection.go +++ b/client/connection.go @@ -14,10 +14,18 @@ import ( // An IRC connection is represented by this struct. type Conn struct { - // Connection Hostname and Nickname - Host string - Me *state.Nick - Network string + // Connection related vars people will care about + Me *state.Nick + Host string + Network string + Connected bool + + // Deprecated: future work to turn Conn into an interface will break this. + // Use the State field to store external state that handlers might need. + State interface{} + + // Contains parameters that people can tweak to change client behaviour. + cfg *Config // Handlers and Commands handlers *hSet @@ -27,21 +35,25 @@ type Conn struct { st state.Tracker stRemovers []Remover - // Use the State field to store external state that handlers might need. - // Remember ... you might need locking for this ;-) - State interface{} - // I/O stuff to server sock net.Conn io *bufio.ReadWriter in chan *Line out chan string - Connected bool // Control channels to goroutines cSend, cLoop, cPing chan bool - // Misc knobs to tweak client behaviour: + // Internal counters for flood protection + badness time.Duration + lastsent time.Time +} + +// Misc knobs to tweak client behaviour go in here +type Config struct { + // Set this to provide the Nick, Ident and Name for the client to use. + Me *state.Nick + // Are we connecting via SSL? Do we care about certificate validity? SSL bool SSLConfig *tls.Config @@ -57,16 +69,9 @@ type Conn struct { // Set this to true to disable flood protection and false to re-enable Flood bool - - // Internal counters for flood protection - badness time.Duration - lastsent time.Time } -// Creates a new IRC connection object, but doesn't connect to anything so -// that you can add event handlers to it. See AddHandler() for details -func Client(nick string, args ...string) *Conn { - logging.InitFromFlags() +func NewConfig(nick string, args ...string) *Config { ident := "goirc" name := "Powered by GoIRC" @@ -76,7 +81,30 @@ func Client(nick string, args ...string) *Conn { if len(args) > 1 && args[1] != "" { name = args[1] } + cfg := &Config{ + PingFreq: 3 * time.Minute, + NewNick: func(s string) string { return s + "_" }, + } + cfg.Me = state.NewNick(nick) + cfg.Me.Ident = ident + cfg.Me.Name = name + return cfg +} + +// Creates a new IRC connection object, but doesn't connect to anything so +// that you can add event handlers to it. See AddHandler() for details +func SimpleClient(nick string, args ...string) (*Conn, error) { + return Client(NewConfig(nick, args...)) +} + +func Client(cfg *Config) (*Conn, error) { + logging.InitFromFlags() + if cfg.Me == nil || cfg.Me.Nick == "" || cfg.Me.Ident == "" { + return nil, fmt.Errorf("Must provide a valid state.Nick in cfg.Me.") + } conn := &Conn{ + Me: cfg.Me, + cfg: cfg, in: make(chan *Line, 32), out: make(chan string, 32), cSend: make(chan bool), @@ -85,17 +113,15 @@ func Client(nick string, args ...string) *Conn { handlers: handlerSet(), commands: commandSet(), stRemovers: make([]Remover, 0, len(stHandlers)), - PingFreq: 3 * time.Minute, - NewNick: func(s string) string { return s + "_" }, lastsent: time.Now(), } conn.addIntHandlers() - conn.Me = state.NewNick(nick) - conn.Me.Ident = ident - conn.Me.Name = name - conn.initialise() - return conn + return conn, nil +} + +func (conn *Conn) Config() *Config { + return conn.cfg } func (conn *Conn) EnableStateTracking() { @@ -142,12 +168,12 @@ func (conn *Conn) Connect(host string, pass ...string) error { conn.Host, host)) } - if conn.SSL { + if conn.cfg.SSL { if !hasPort(host) { host += ":6697" } logging.Info("irc.Connect(): Connecting to %s with SSL.", host) - if s, err := tls.Dial("tcp", host, conn.SSLConfig); err == nil { + if s, err := tls.Dial("tcp", host, conn.cfg.SSLConfig); err == nil { conn.sock = s } else { return err @@ -182,7 +208,7 @@ func (conn *Conn) postConnect() { bufio.NewWriter(conn.sock)) go conn.send() go conn.recv() - if conn.PingFreq > 0 { + if conn.cfg.PingFreq > 0 { go conn.ping() } else { // Otherwise the send in shutdown will hang :-/ @@ -232,7 +258,7 @@ func (conn *Conn) recv() { // Repeatedly pings the server every PingFreq seconds (no matter what) func (conn *Conn) ping() { - tick := time.NewTicker(conn.PingFreq) + tick := time.NewTicker(conn.cfg.PingFreq) for { select { case <-tick.C: @@ -258,9 +284,9 @@ func (conn *Conn) runLoop() { } // Write a \r\n terminated line of output to the connected server, -// using Hybrid's algorithm to rate limit if conn.Flood is false. +// using Hybrid's algorithm to rate limit if conn.cfg.Flood is false. func (conn *Conn) write(line string) { - if !conn.Flood { + if !conn.cfg.Flood { if t := conn.rateLimit(len(line)); t != 0 { // sleep for the current line's time value before sending it logging.Debug("irc.rateLimit(): Flood! Sleeping for %.2f secs.", @@ -303,7 +329,7 @@ func (conn *Conn) rateLimit(chars int) time.Duration { func (conn *Conn) shutdown() { // Guard against double-call of shutdown() if we get an error in send() - // as calling sock.Close() will cause recv() to recieve EOF in readstring() + // as calling sock.Close() will cause recv() to receive EOF in readstring() if conn.Connected { logging.Info("irc.shutdown(): Disconnected from server.") conn.dispatch(&Line{Cmd: "disconnected"}) diff --git a/client/connection_test.go b/client/connection_test.go index f8b5950..471981e 100644 --- a/client/connection_test.go +++ b/client/connection_test.go @@ -21,12 +21,12 @@ func setUp(t *testing.T, start ...bool) (*Conn, *testState) { ctrl := gomock.NewController(t) st := state.NewMockTracker(ctrl) nc := MockNetConn(t) - c := Client("test", "test", "Testing IRC") + c, _ := SimpleClient("test", "test", "Testing IRC") logging.SetLogLevel(logging.LogFatal) c.st = st c.sock = nc - c.Flood = true // Tests can take a while otherwise + c.cfg.Flood = true // Tests can take a while otherwise c.Connected = true if len(start) == 0 { // Hack to allow tests of send, recv, write etc. @@ -82,7 +82,7 @@ func TestEOF(t *testing.T) { func TestClientAndStateTracking(t *testing.T) { ctrl := gomock.NewController(t) st := state.NewMockTracker(ctrl) - c := Client("test", "test", "Testing IRC") + c, _ := SimpleClient("test", "test", "Testing IRC") // Assert some basic things about the initial state of the Conn struct if c.Me.Nick != "test" || c.Me.Ident != "test" || @@ -278,7 +278,7 @@ func TestPing(t *testing.T) { defer s.ctrl.Finish() // Set a low ping frequency for testing. - c.PingFreq = 50 * time.Millisecond + c.cfg.PingFreq = 50 * time.Millisecond // reader is a helper to do a "non-blocking" read of c.out reader := func() string { @@ -429,13 +429,13 @@ func TestWrite(t *testing.T) { c.write("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.cfg.Flood = true -- so we should // not have set c.badness at this point. if c.badness != 0 { t.Errorf("Flood control used when Flood = true.") } - c.Flood = false + c.cfg.Flood = false c.write("she so useless") s.nc.Expect("she so useless") diff --git a/client/handlers.go b/client/handlers.go index 6b34977..9829f40 100644 --- a/client/handlers.go +++ b/client/handlers.go @@ -54,7 +54,7 @@ func (conn *Conn) h_001(line *Line) { // Handler to deal with "433 :Nickname already in use" func (conn *Conn) h_433(line *Line) { // Args[1] is the new nick we were attempting to acquire - neu := conn.NewNick(line.Args[1]) + neu := conn.cfg.NewNick(line.Args[1]) conn.Nick(neu) // if this is happening before we're properly connected (i.e. the nick // we sent in the initial NICK command is in use) we will not receive @@ -87,7 +87,7 @@ func (conn *Conn) h_NICK(line *Line) { // Handle PRIVMSGs that trigger Commands func (conn *Conn) h_PRIVMSG(line *Line) { txt := line.Args[1] - if conn.CommandStripNick && strings.HasPrefix(txt, conn.Me.Nick) { + if conn.cfg.CommandStripNick && strings.HasPrefix(txt, conn.Me.Nick) { // Look for '^${nick}[:;>,-]? ' l := len(conn.Me.Nick) switch txt[l] { @@ -102,7 +102,7 @@ func (conn *Conn) h_PRIVMSG(line *Line) { if cmd == nil { return } - if conn.CommandStripPrefix { + if conn.cfg.CommandStripPrefix { txt = strings.TrimSpace(txt[l:]) } if txt != line.Args[1] { diff --git a/client/handlers_test.go b/client/handlers_test.go index 1231bab..f7ae665 100644 --- a/client/handlers_test.go +++ b/client/handlers_test.go @@ -157,26 +157,26 @@ func TestPRIVMSG(t *testing.T) { c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar")) s.nc.ExpectNothing() - c.CommandStripNick = true + c.cfg.CommandStripNick = true c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar")) s.nc.Expect("PRIVMSG #foo :prefix bar") c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar")) s.nc.Expect("PRIVMSG #foo :prefix bar") - c.CommandStripPrefix = true + c.cfg.CommandStripPrefix = true c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar")) s.nc.Expect("PRIVMSG #foo :bar") c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar")) s.nc.Expect("PRIVMSG #foo :bar") - c.CommandStripNick = false + c.cfg.CommandStripNick = false c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :prefix bar")) s.nc.Expect("PRIVMSG #foo :bar") c.h_PRIVMSG(parseLine(":blah!moo@cows.com PRIVMSG #foo :test: prefix bar")) s.nc.ExpectNothing() // Check the various nick addressing notations that are supported. - c.CommandStripNick = true + c.cfg.CommandStripNick = true for _, addr := range []string{":", ";", ",", ">", "-", ""} { c.h_PRIVMSG(parseLine(fmt.Sprintf( ":blah!moo@cows.com PRIVMSG #foo :test%s prefix bar", addr)))