From a19bce59988eb34ba093e7b2a947b4733bd98d47 Mon Sep 17 00:00:00 2001 From: raylu Date: Fri, 28 Jan 2011 21:03:45 -0500 Subject: [PATCH 01/12] Make a timeout so that Conn.Err closes when the connection is lost --- client/connection.go | 1 + 1 file changed, 1 insertion(+) diff --git a/client/connection.go b/client/connection.go index 5afe9f4..4b04dd5 100644 --- a/client/connection.go +++ b/client/connection.go @@ -140,6 +140,7 @@ func (conn *Conn) Connect(host string, pass ...string) os.Error { conn.io = bufio.NewReadWriter( bufio.NewReader(conn.sock), bufio.NewWriter(conn.sock)) + conn.sock.SetTimeout(300000000000) // 5 minutes go conn.send() go conn.recv() From 83b482f8ceb1dc928c2fd5b55d5b8aef892fd433 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Sun, 17 Jul 2011 13:48:12 +0100 Subject: [PATCH 02/12] Make socket timeouts a configurable thingy. --- client/connection.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/connection.go b/client/connection.go index 4b04dd5..3be02e4 100644 --- a/client/connection.go +++ b/client/connection.go @@ -40,6 +40,9 @@ type Conn struct { SSL bool SSLConfig *tls.Config + // Socket timeout, in seconds. Defaulted to 5m in New(). + Timeout int + // Set this to true to disable flood protection and false to re-enable Flood bool @@ -70,6 +73,7 @@ func New(nick, user, name string) *Conn { conn.initialise() conn.SSL = false conn.SSLConfig = nil + conn.Timeout = 300 conn.Me = conn.NewNick(nick, user, name, "") conn.Timestamp = time.LocalTime conn.TSFormat = "15:04:05" @@ -140,7 +144,7 @@ func (conn *Conn) Connect(host string, pass ...string) os.Error { conn.io = bufio.NewReadWriter( bufio.NewReader(conn.sock), bufio.NewWriter(conn.sock)) - conn.sock.SetTimeout(300000000000) // 5 minutes + conn.sock.SetTimeout(conn.Timeout * 1e9) go conn.send() go conn.recv() From 50276464781a4c104dddda654fc89f17b0b5189a Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Mon, 18 Jul 2011 09:14:58 +0100 Subject: [PATCH 03/12] gofix run --- client/connection.go | 6 +++--- client/nickchan.go | 42 +++++++++++++++++++++--------------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/client/connection.go b/client/connection.go index 3be02e4..a18a4e7 100644 --- a/client/connection.go +++ b/client/connection.go @@ -50,7 +50,7 @@ type Conn struct { Timestamp func() *time.Time // Enable debugging? Set format for timestamps on debug output. - Debug bool + Debug bool TSFormat string } @@ -117,7 +117,7 @@ func (conn *Conn) Connect(host string, pass ...string) os.Error { // It's unfortunate that tls.Dial doesn't allow a tls.Config arg, // so we simply replicate it here with the correct Config. // http://codereview.appspot.com/2883041 - if s, err := net.Dial("tcp", "", host); err == nil { + if s, err := net.Dial("tcp", host); err == nil { // Passing nil config => certs are validated. c := tls.Client(s, conn.SSLConfig) if err = c.Handshake(); err == nil { @@ -133,7 +133,7 @@ func (conn *Conn) Connect(host string, pass ...string) os.Error { if !hasPort(host) { host += ":6667" } - if s, err := net.Dial("tcp", "", host); err == nil { + if s, err := net.Dial("tcp", host); err == nil { conn.sock = s } else { return err diff --git a/client/nickchan.go b/client/nickchan.go index 87f1da7..fdf79b1 100644 --- a/client/nickchan.go +++ b/client/nickchan.go @@ -402,23 +402,23 @@ func (n *Nick) String() string { func (cm *ChanMode) String() string { str := "+" a := make([]string, 2) - v := reflect.Indirect(reflect.NewValue(cm)).(*reflect.StructValue) - t := v.Type().(*reflect.StructType) + v := reflect.Indirect(reflect.ValueOf(cm)) + t := v.Type() for i := 0; i < v.NumField(); i++ { - switch f := v.Field(i).(type) { - case *reflect.BoolValue: - if f.Get() { + switch f := v.Field(i); f.Kind() { + case reflect.Bool: + if f.Bool() { str += ChanModeToString[t.Field(i).Name] } - case *reflect.StringValue: - if f.Get() != "" { + case reflect.String: + if f.String() != "" { str += ChanModeToString[t.Field(i).Name] - a[0] = f.Get() + a[0] = f.String() } - case *reflect.IntValue: - if f.Get() != 0 { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if f.Int() != 0 { str += ChanModeToString[t.Field(i).Name] - a[1] = fmt.Sprintf("%d", f.Get()) + a[1] = fmt.Sprintf("%d", f.Int()) } } } @@ -437,13 +437,13 @@ func (cm *ChanMode) String() string { // +iwx func (nm *NickMode) String() string { str := "+" - v := reflect.Indirect(reflect.NewValue(nm)).(*reflect.StructValue) - t := v.Type().(*reflect.StructType) + v := reflect.Indirect(reflect.ValueOf(nm)) + t := v.Type() for i := 0; i < v.NumField(); i++ { - switch f := v.Field(i).(type) { + switch f := v.Field(i); f.Kind() { // only bools here at the mo! - case *reflect.BoolValue: - if f.Get() { + case reflect.Bool: + if f.Bool() { str += NickModeToString[t.Field(i).Name] } } @@ -458,13 +458,13 @@ func (nm *NickMode) String() string { // +o func (p *ChanPrivs) String() string { str := "+" - v := reflect.Indirect(reflect.NewValue(p)).(*reflect.StructValue) - t := v.Type().(*reflect.StructType) + v := reflect.Indirect(reflect.ValueOf(p)) + t := v.Type() for i := 0; i < v.NumField(); i++ { - switch f := v.Field(i).(type) { + switch f := v.Field(i); f.Kind() { // only bools here at the mo too! - case *reflect.BoolValue: - if f.Get() { + case reflect.Bool: + if f.Bool() { str += ChanPrivToString[t.Field(i).Name] } } From 9748f9c47ec5dcd4e8ac710dd7fcb08694e0ee7c Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Thu, 21 Jul 2011 21:59:01 +0100 Subject: [PATCH 04/12] Oops, timeouts are int64 nanoseconds. --- client/connection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/connection.go b/client/connection.go index a18a4e7..c1309ca 100644 --- a/client/connection.go +++ b/client/connection.go @@ -41,7 +41,7 @@ type Conn struct { SSLConfig *tls.Config // Socket timeout, in seconds. Defaulted to 5m in New(). - Timeout int + Timeout int64 // Set this to true to disable flood protection and false to re-enable Flood bool From a5a4f989ac4fef77a7bd9ae3ab69d6bd504ff6bf Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Thu, 21 Jul 2011 22:00:45 +0100 Subject: [PATCH 05/12] Update SSL connectivity to use tls.Dial now it supports a config arg. --- client/connection.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/client/connection.go b/client/connection.go index c1309ca..746a65a 100644 --- a/client/connection.go +++ b/client/connection.go @@ -114,18 +114,8 @@ func (conn *Conn) Connect(host string, pass ...string) os.Error { if !hasPort(host) { host += ":6697" } - // It's unfortunate that tls.Dial doesn't allow a tls.Config arg, - // so we simply replicate it here with the correct Config. - // http://codereview.appspot.com/2883041 - if s, err := net.Dial("tcp", host); err == nil { - // Passing nil config => certs are validated. - c := tls.Client(s, conn.SSLConfig) - if err = c.Handshake(); err == nil { - conn.sock = c - } else { - s.Close() - return err - } + if s, err := tls.Dial("tcp", host, conn.SSLConfig); err == nil { + conn.sock = s } else { return err } From eb515580095dbe5f37187a7325d437180d404046 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Thu, 21 Jul 2011 23:03:11 +0100 Subject: [PATCH 06/12] Fix for issues/6 (1/2): Don't close channels when disconnected. --- client/connection.go | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/client/connection.go b/client/connection.go index 746a65a..2e27798 100644 --- a/client/connection.go +++ b/client/connection.go @@ -69,29 +69,30 @@ type Line struct { // 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 New(nick, user, name string) *Conn { - conn := new(Conn) - conn.initialise() - conn.SSL = false - conn.SSLConfig = nil - conn.Timeout = 300 + conn := &Conn{ + in: make(chan *Line, 32), + out: make(chan string, 32), + Err: make(chan os.Error, 4), + SSL: false, + SSLConfig: nil, + Timeout: 300, + Timestamp: time.LocalTime, + TSFormat: "15:04:05", + } conn.Me = conn.NewNick(nick, user, name, "") - conn.Timestamp = time.LocalTime - conn.TSFormat = "15:04:05" + conn.initialise() conn.setupEvents() return conn } +// Per-connection state initialisation. func (conn *Conn) initialise() { - // allocate meh some memoraaaahh conn.nicks = make(map[string]*Nick) conn.chans = make(map[string]*Channel) - conn.in = make(chan *Line, 32) - conn.out = make(chan string, 32) - conn.Err = make(chan os.Error, 4) conn.io = nil conn.sock = nil - // if this is being called because we are reconnecting, conn.Me + // If this is being called because we are reconnecting, conn.Me // will still have all the old channels referenced -- nuke them! if conn.Me != nil { conn.Me = conn.NewNick(conn.Me.Nick, conn.Me.Ident, conn.Me.Name, "") @@ -254,9 +255,6 @@ func (conn *Conn) runLoop() { } func (conn *Conn) shutdown() { - close(conn.in) - close(conn.out) - close(conn.Err) conn.connected = false conn.sock.Close() // reinit datastructures ready for next connection From 0200b741dcef3886567edf72bfe56efb4458c70d Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Fri, 22 Jul 2011 01:08:42 +0100 Subject: [PATCH 07/12] Fix for issues/6 (2/2): Move to using control channels and select. --- client.go | 27 ++++++----- client/connection.go | 105 +++++++++++++++++++++++++++++-------------- 2 files changed, 88 insertions(+), 44 deletions(-) diff --git a/client.go b/client.go index 7e16004..d2040d1 100644 --- a/client.go +++ b/client.go @@ -15,6 +15,11 @@ func main() { c.AddHandler("connected", func(conn *irc.Conn, line *irc.Line) { conn.Join("#go-nuts") }) + // Set up a handler to notify of disconnect events. + quit := make(chan bool) + c.AddHandler("disconnected", + func(conn *irc.Conn, line *irc.Line) { quit <- true }) + // connect to server if err := c.Connect("irc.freenode.net"); err != nil { fmt.Printf("Connection error: %s\n", err) @@ -74,18 +79,18 @@ func main() { } }() - // stall here waiting for asplode on error channel - for { - for err := range c.Err { + for !reallyquit { + select { + case err := <-c.Err: fmt.Printf("goirc error: %s\n", err) - } - if reallyquit { - break - } - fmt.Println("Reconnecting...") - if err := c.Connect("irc.freenode.net"); err != nil { - fmt.Printf("Connection error: %s\n", err) - break + case <-quit: + if !reallyquit { + fmt.Println("Reconnecting...") + if err := c.Connect("irc.freenode.net"); err != nil { + fmt.Printf("Connection error: %s\n", err) + reallyquit = true + } + } } } } diff --git a/client/connection.go b/client/connection.go index 2e27798..81af227 100644 --- a/client/connection.go +++ b/client/connection.go @@ -10,6 +10,10 @@ import ( "time" ) +const ( + second = int64(1e9) +) + // An IRC connection is represented by this struct. Once connected, any errors // encountered are piped down *Conn.Err; this channel is closed on disconnect. type Conn struct { @@ -32,6 +36,9 @@ type Conn struct { out chan string connected bool + // Control channels to goroutines + cSend, cLoop chan bool + // Error channel to transmit any fail back to the user Err chan os.Error @@ -46,6 +53,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, lastsent int64 + // Function which returns a *time.Time for use as a timestamp Timestamp func() *time.Time @@ -73,9 +83,14 @@ func New(nick, user, name string) *Conn { in: make(chan *Line, 32), out: make(chan string, 32), Err: make(chan os.Error, 4), + cSend: make(chan bool), + cLoop: make(chan bool), SSL: false, SSLConfig: nil, Timeout: 300, + Flood: false, + badness: 0, + lastsent: 0, Timestamp: time.LocalTime, TSFormat: "15:04:05", } @@ -159,38 +174,15 @@ func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } -// dispatch input from channel as \r\n terminated line to peer -// flood controlled using hybrid's algorithm if conn.Flood is true +// goroutine to pass data from output channel to write() func (conn *Conn) send() { - lastsent := time.Nanoseconds() - var badness, linetime, second int64 = 0, 0, 1000000000 - for line := range conn.out { - // 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 + int64(len(line))*second/120 - if !conn.Flood && conn.connected { - // No point in tallying up flood protection stuff until connected - if badness += linetime + lastsent - time.Nanoseconds(); badness < 0 { - // negative badness times are badness... - badness = int64(0) - } - } - lastsent = time.Nanoseconds() - - // 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 badness > 10*second && !conn.Flood { - // so sleep for the current line's time value before sending it - time.Sleep(linetime) - } - if _, err := conn.io.WriteString(line + "\r\n"); err != nil { - conn.error("irc.send(): %s", err.String()) - conn.shutdown() - break - } - conn.io.Flush() - if conn.Debug { - fmt.Println(conn.Timestamp().Format(conn.TSFormat) + " -> " + line) + for { + select { + case line := <-conn.out: + conn.write(line) + case <-conn.cSend: + // strobe on control channel, bail out + return } } } @@ -248,15 +240,62 @@ func (conn *Conn) recv() { } } +// goroutine to dispatch events for lines received on input channel func (conn *Conn) runLoop() { - for line := range conn.in { - conn.dispatchEvent(line) + for { + select { + case line := <-conn.in: + conn.dispatchEvent(line) + case <-conn.cLoop: + // strobe on control channel, bail out + return + } + } +} + +// Write a \r\n terminated line of output to the connected server, +// using Hybrid's algorithm to rate limit if conn.Flood is false. +func (conn *Conn) write(line string) { + if !conn.Flood { + conn.rateLimit(int64(len(line))) + } + + if _, err := conn.io.WriteString(line + "\r\n"); err != nil { + conn.error("irc.send(): %s", err.String()) + conn.shutdown() + return + } + conn.io.Flush() + if conn.Debug { + fmt.Println(conn.Timestamp().Format(conn.TSFormat) + " -> " + line) + } +} + +// Implement Hybrid's flood control algorithm to rate-limit outgoing lines. +func (conn *Conn) rateLimit(chars int64) { + // 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 + if conn.badness += linetime - elapsed; conn.badness < 0 { + // negative badness times are badness... + conn.badness = int64(0) + } + conn.lastsent = time.Nanoseconds() + // 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 && !conn.Flood { + // so sleep for the current line's time value before sending it + time.Sleep(linetime) } } func (conn *Conn) shutdown() { conn.connected = false conn.sock.Close() + conn.cSend <- true + conn.cLoop <- true + conn.dispatchEvent(&Line{Cmd: "DISCONNECTED"}) // reinit datastructures ready for next connection // do this here rather than after runLoop()'s for due to race conn.initialise() From 291132cab52509abfe60bd7d0b06401a6b6ee571 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Fri, 22 Jul 2011 01:11:15 +0100 Subject: [PATCH 08/12] Refactor recv() since we needed to rewrite send(). --- client/Makefile | 1 + client/connection.go | 50 +++------------------------------------ client/line.go | 56 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 47 deletions(-) create mode 100644 client/line.go diff --git a/client/Makefile b/client/Makefile index 35dfe19..48c3976 100644 --- a/client/Makefile +++ b/client/Makefile @@ -9,6 +9,7 @@ GOFILES=\ connection.go\ commands.go\ handlers.go\ + line.go\ nickchan.go include $(GOROOT)/src/Make.pkg diff --git a/client/connection.go b/client/connection.go index 81af227..32ff9d3 100644 --- a/client/connection.go +++ b/client/connection.go @@ -64,18 +64,6 @@ type Conn struct { TSFormat string } -// We parse an incoming line into this struct. Line.Cmd is used as the trigger -// name for incoming event handlers, see *Conn.recv() for details. -// Raw =~ ":nick!user@host cmd args[] :text" -// Src == "nick!user@host" -// Cmd == e.g. PRIVMSG, 332 -type Line struct { - Nick, Ident, Host, Src string - Cmd, Raw string - Args []string - Time *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 New(nick, user, name string) *Conn { @@ -191,51 +179,19 @@ func (conn *Conn) send() { func (conn *Conn) recv() { for { s, err := conn.io.ReadString('\n') - t := conn.Timestamp() if err != nil { conn.error("irc.recv(): %s", err.String()) conn.shutdown() break } s = strings.Trim(s, "\r\n") + t := conn.Timestamp() if conn.Debug { fmt.Println(t.Format(conn.TSFormat) + " <- " + s) } - line := &Line{Raw: s, Time: t} - if s[0] == ':' { - // remove a source and parse it - if idx := strings.Index(s, " "); idx != -1 { - line.Src, s = s[1:idx], s[idx+1:len(s)] - } else { - // pretty sure we shouldn't get here ... - line.Src = s[1:len(s)] - conn.in <- line - continue - } - - // src can be the hostname of the irc server or a nick!user@host - line.Host = line.Src - nidx, uidx := strings.Index(line.Src, "!"), strings.Index(line.Src, "@") - if uidx != -1 && nidx != -1 { - line.Nick = line.Src[0:nidx] - line.Ident = line.Src[nidx+1 : uidx] - line.Host = line.Src[uidx+1 : len(line.Src)] - } - } - - // now we're here, we've parsed a :nick!user@host or :server off - // s should contain "cmd args[] :text" - args := strings.Split(s, " :", 2) - if len(args) > 1 { - args = append(strings.Fields(args[0]), args[1]) - } else { - args = strings.Fields(args[0]) - } - line.Cmd = strings.ToUpper(args[0]) - if len(args) > 1 { - line.Args = args[1:len(args)] - } + line := parseLine(s) + line.Time = t conn.in <- line } } diff --git a/client/line.go b/client/line.go new file mode 100644 index 0000000..d644add --- /dev/null +++ b/client/line.go @@ -0,0 +1,56 @@ +package client + +import ( + "strings" + "time" +) + +// We parse an incoming line into this struct. Line.Cmd is used as the trigger +// name for incoming event handlers, see *Conn.recv() for details. +// Raw =~ ":nick!user@host cmd args[] :text" +// Src == "nick!user@host" +// Cmd == e.g. PRIVMSG, 332 +type Line struct { + Nick, Ident, Host, Src string + Cmd, Raw string + Args []string + Time *time.Time +} + +func parseLine(s string) *Line { + line := &Line{Raw: s} + if s[0] == ':' { + // remove a source and parse it + if idx := strings.Index(s, " "); idx != -1 { + line.Src, s = s[1:idx], s[idx+1:len(s)] + } else { + // pretty sure we shouldn't get here ... + line.Src = s[1:len(s)] + return line + } + + // src can be the hostname of the irc server or a nick!user@host + line.Host = line.Src + nidx, uidx := strings.Index(line.Src, "!"), strings.Index(line.Src, "@") + if uidx != -1 && nidx != -1 { + line.Nick = line.Src[0:nidx] + line.Ident = line.Src[nidx+1 : uidx] + line.Host = line.Src[uidx+1 : len(line.Src)] + } + } + + // now we're here, we've parsed a :nick!user@host or :server off + // s should contain "cmd args[] :text" + args := strings.Split(s, " :", 2) + if len(args) > 1 { + args = append(strings.Fields(args[0]), args[1]) + } else { + args = strings.Fields(args[0]) + } + line.Cmd = strings.ToUpper(args[0]) + if len(args) > 1 { + line.Args = args[1:len(args)] + } + return line +} + From 6634869fe600be897ca521d39e02824ad8f025fc Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Fri, 22 Jul 2011 01:17:35 +0100 Subject: [PATCH 09/12] Use connected bool properly to ensure shutdown() can't be called twice. --- client/connection.go | 21 +++++++++++++-------- client/handlers.go | 3 +-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/client/connection.go b/client/connection.go index 32ff9d3..892bf8a 100644 --- a/client/connection.go +++ b/client/connection.go @@ -139,6 +139,7 @@ func (conn *Conn) Connect(host string, pass ...string) os.Error { bufio.NewReader(conn.sock), bufio.NewWriter(conn.sock)) conn.sock.SetTimeout(conn.Timeout * 1e9) + conn.connected = true go conn.send() go conn.recv() @@ -247,14 +248,18 @@ func (conn *Conn) rateLimit(chars int64) { } func (conn *Conn) shutdown() { - conn.connected = false - conn.sock.Close() - conn.cSend <- true - conn.cLoop <- true - conn.dispatchEvent(&Line{Cmd: "DISCONNECTED"}) - // reinit datastructures ready for next connection - // do this here rather than after runLoop()'s for due to race - conn.initialise() + // 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() + if conn.connected { + conn.connected = false + conn.sock.Close() + conn.cSend <- true + conn.cLoop <- true + conn.dispatchEvent(&Line{Cmd: "DISCONNECTED"}) + // reinit datastructures ready for next connection + // do this here rather than after runLoop()'s for due to race + conn.initialise() + } } // Dumps a load of information about the current state of the connection to a diff --git a/client/handlers.go b/client/handlers.go index 75ca25a..d3f896d 100644 --- a/client/handlers.go +++ b/client/handlers.go @@ -76,7 +76,6 @@ func (conn *Conn) h_PING(line *Line) { // Handler to trigger a "CONNECTED" event on receipt of numeric 001 func (conn *Conn) h_001(line *Line) { // we're connected! - conn.connected = true conn.dispatchEvent(&Line{Cmd: "CONNECTED"}) // and we're being given our hostname (from the server's perspective) t := line.Args[len(line.Args)-1] @@ -103,7 +102,7 @@ func (conn *Conn) h_433(line *Line) { // 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 // a NICK message to confirm our change of nick, so ReNick here... - if !conn.connected && line.Args[1] == conn.Me.Nick { + if line.Args[1] == conn.Me.Nick { conn.Me.ReNick(line.Args[1] + "_") } } From 07d5c0676e02a27683b93835eba57fa53aaa3de8 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Fri, 22 Jul 2011 01:20:07 +0100 Subject: [PATCH 10/12] Fix compile error when NewNick() is called before initialise(). --- client/connection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/connection.go b/client/connection.go index 892bf8a..f44a22b 100644 --- a/client/connection.go +++ b/client/connection.go @@ -82,9 +82,9 @@ func New(nick, user, name string) *Conn { Timestamp: time.LocalTime, TSFormat: "15:04:05", } - conn.Me = conn.NewNick(nick, user, name, "") conn.initialise() conn.setupEvents() + conn.Me = conn.NewNick(nick, user, name, "") return conn } From c3715be8292952de22fcb43874dce358cddf336f Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Fri, 22 Jul 2011 01:20:25 +0100 Subject: [PATCH 11/12] Use second constant in SetTimeout(). --- client/connection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/connection.go b/client/connection.go index f44a22b..73706f3 100644 --- a/client/connection.go +++ b/client/connection.go @@ -138,7 +138,7 @@ func (conn *Conn) Connect(host string, pass ...string) os.Error { conn.io = bufio.NewReadWriter( bufio.NewReader(conn.sock), bufio.NewWriter(conn.sock)) - conn.sock.SetTimeout(conn.Timeout * 1e9) + conn.sock.SetTimeout(conn.Timeout * second) conn.connected = true go conn.send() go conn.recv() From 830dbcbb7fdecd0f9aecf3009ef9652ca3adfb97 Mon Sep 17 00:00:00 2001 From: Alex Bramley Date: Fri, 22 Jul 2011 01:26:41 +0100 Subject: [PATCH 12/12] Update the docs slightly. --- README.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c0e711b..9c1e5f1 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,20 @@ Synopsis: c.Debug = true // Optionally, enable SSL c.SSL = true - // add handlers to do things here! - if err := c.Connect("irc.freenode.net"); err != nil { + + // Add handlers to do things here! + // e.g. watching for disconnection from the server. + connected := true + c.AddHandler("disconnected", + func(conn *irc.Conn, line *irc.Line) { connected = false }) + + // Tell client to connect + if err := c.Connect("irc.freenode.net"); err != nil { fmt.Printf("Connection error: %s\n", err.String()) } - for { - if closed(c.Err) { - break - } + + // Loop until client gets disconnected, printing any errors + for connected { if err := <-c.Err; err != nil { fmt.Printf("goirc error: %s", err.String()) } @@ -61,5 +67,5 @@ indebted to Matt Gruen for his work on the re-organisation and channel-based communication structure of `*Conn.send()` and `*Conn.recv()`. I'm sure things could be more asynchronous, still. -This code is (c) 2009-10 Alex Bramley, and released under the same licence terms +This code is (c) 2009-11 Alex Bramley, and released under the same licence terms as Go itself.