Merge remote-tracking branch 'upstream/master'

Conflicts:
	client/connection.go
This commit is contained in:
kyle 2014-12-20 19:17:43 +02:00
commit d1aa016bb0
4 changed files with 30 additions and 259 deletions

View File

@ -31,6 +31,7 @@ type Conn struct {
stRemovers []Remover stRemovers []Remover
// I/O stuff to server // I/O stuff to server
dialer *net.Dialer
sock net.Conn sock net.Conn
io *bufio.ReadWriter io *bufio.ReadWriter
in chan *Line in chan *Line
@ -58,6 +59,9 @@ type Config struct {
SSL bool SSL bool
SSLConfig *tls.Config SSLConfig *tls.Config
// Local address to connect to the server.
LocalAddr string
// Replaceable function to customise the 433 handler's new nick // Replaceable function to customise the 433 handler's new nick
NewNick func(string) string NewNick func(string) string
@ -91,7 +95,7 @@ func NewConfig(nick string, args ...string) *Config {
NewNick: func(s string) string { return s + "_" }, NewNick: func(s string) string { return s + "_" },
Recover: (*Conn).LogPanic, // in dispatch.go Recover: (*Conn).LogPanic, // in dispatch.go
SplitLen: 450, SplitLen: 450,
Timeout: 60, Timeout: 60 * time.Second,
} }
cfg.Me.Ident = "goirc" cfg.Me.Ident = "goirc"
if len(args) > 0 && args[0] != "" { if len(args) > 0 && args[0] != "" {
@ -122,8 +126,24 @@ func Client(cfg *Config) *Conn {
cfg.Me.Ident = "goirc" cfg.Me.Ident = "goirc"
cfg.Me.Name = "Powered by GoIRC" cfg.Me.Name = "Powered by GoIRC"
} }
dialer := new(net.Dialer)
if cfg.LocalAddr != "" {
if !hasPort(cfg.LocalAddr) {
cfg.LocalAddr += ":0"
}
local, err := net.ResolveTCPAddr("tcp", cfg.LocalAddr)
if err == nil {
dialer.LocalAddr = local
} else {
logging.Error("irc.Client(): Cannot resolve local address %s: %s", cfg.LocalAddr, err)
}
}
conn := &Conn{ conn := &Conn{
cfg: cfg, cfg: cfg,
dialer: dialer,
in: make(chan *Line, 32), in: make(chan *Line, 32),
out: make(chan string, 32), out: make(chan string, 32),
intHandlers: handlerSet(), intHandlers: handlerSet(),
@ -209,29 +229,27 @@ func (conn *Conn) Connect() error {
} }
if conn.cfg.SSL { if conn.cfg.SSL {
if !hasPort(conn.cfg.Server) { if !hasPort(conn.cfg.Server) {
conn.cfg.Server += ":6697" conn.cfg.Server = net.JoinHostPort(conn.cfg.Server, "6697")
} }
if &conn.cfg.Timeout != nil { if &conn.cfg.Timeout != nil {
conn.cfg.Timeout = (60 * time.Second) conn.cfg.Timeout = (60 * time.Second)
} }
logging.Info("irc.Connect(): Connecting to %s with SSL.", conn.cfg.Server) logging.Info("irc.Connect(): Connecting to %s with SSL.", conn.cfg.Server)
dialer := &net.Dialer{ conn.dialer.Timeout = conn.cfg.Timeout
Timeout: conn.cfg.Timeout, if s, err := tls.DialWithDialer(conn.dialer, "tcp", conn.cfg.Server, conn.cfg.SSLConfig); err == nil {
}
if s, err := tls.DialWithDialer(dialer, "tcp", conn.cfg.Server, conn.cfg.SSLConfig); err == nil {
conn.sock = s conn.sock = s
} else { } else {
return err return err
} }
} else { } else {
if !hasPort(conn.cfg.Server) { if !hasPort(conn.cfg.Server) {
conn.cfg.Server += ":6667" conn.cfg.Server = net.JoinHostPort(conn.cfg.Server, "6667")
} }
if &conn.cfg.Timeout != nil { if &conn.cfg.Timeout != nil {
conn.cfg.Timeout = (60 * time.Second) conn.cfg.Timeout = (60 * time.Second)
} }
logging.Info("irc.Connect(): Connecting to %s without SSL.", conn.cfg.Server) logging.Info("irc.Connect(): Connecting to %s without SSL.", conn.cfg.Server)
if s, err := net.DialTimeout("tcp", conn.cfg.Server, conn.cfg.Timeout); err == nil { if s, err := conn.dialer.DialTimeout("tcp", conn.cfg.Server, conn.cfg.Timeout); err == nil {
conn.sock = s conn.sock = s
} else { } else {
return err return err
@ -239,7 +257,7 @@ func (conn *Conn) Connect() error {
} }
conn.connected = true conn.connected = true
conn.postConnect(true) conn.postConnect(true)
conn.dispatch(&Line{Cmd: REGISTER}) conn.dispatch(&Line{Cmd: REGISTER, Time: time.Now()})
return nil return nil
} }
@ -393,7 +411,7 @@ func (conn *Conn) shutdown() {
conn.wg.Wait() conn.wg.Wait()
// reinit datastructures ready for next connection // reinit datastructures ready for next connection
conn.initialise() conn.initialise()
conn.dispatch(&Line{Cmd: DISCONNECTED}) conn.dispatch(&Line{Cmd: DISCONNECTED, Time: time.Now()})
} }
// Dumps a load of information about the current state of the connection to a // Dumps a load of information about the current state of the connection to a

View File

@ -5,6 +5,7 @@ package client
import ( import (
"strings" "strings"
"time"
) )
// sets up the internal event handlers to do essential IRC protocol things // sets up the internal event handlers to do essential IRC protocol things
@ -42,7 +43,7 @@ func (conn *Conn) h_REGISTER(line *Line) {
// Handler to trigger a CONNECTED event on receipt of numeric 001 // Handler to trigger a CONNECTED event on receipt of numeric 001
func (conn *Conn) h_001(line *Line) { func (conn *Conn) h_001(line *Line) {
// we're connected! // we're connected!
conn.dispatch(&Line{Cmd: CONNECTED}) conn.dispatch(&Line{Cmd: CONNECTED, Time: time.Now()})
// and we're being given our hostname (from the server's perspective) // and we're being given our hostname (from the server's perspective)
t := line.Args[len(line.Args)-1] t := line.Args[len(line.Args)-1]
if idx := strings.LastIndex(t, " "); idx != -1 { if idx := strings.LastIndex(t, " "); idx != -1 {

View File

@ -1,35 +0,0 @@
## client api changes
`goirc.go` is a fix compatible with `go tool fix` that attempts to
programaticallly change old client code to work with the newer, shinier
API. It might even work, depending on the complexity of your code. You'll
need to have a Go source tree in the path specified by GOROOT. GOOS and
GOARCH may not be set in your environment; use your head.
### To install:
cd $GOROOT/src/cmd/fix
ln -s $GOPATH/src/github.com/fluffle/goirc/fix/goirc.go
go build
mv fix $GOROOT/pkg/tool/$GOOS_$GOARCH
### To fix:
go tool fix -r goirc -diff /path/to/code
<check diffs>
go tool fix -r goirc /path/to/code
### Things that aren't fixed by this
This fix doesn't take care of some bad design decisions I made:
- conn.State is left as-is. If you're using this you'll need to rewrite
things to get scope into your handlers in a different fashion.
- conn.ER and conn.ED are left as-is. These should be completely removed
(and probably shouldn't have been left accessible in the first place).
It's also quite likely that this won't produce the nicest "fixed" code. In
particular, if you're seeing lots of lines like `conn.Config().XXX = "foo"`,
you probably want to consider creating a `Config` struct and then passing
it to `client.Client()`.

View File

@ -1,213 +0,0 @@
package main
import (
"fmt"
"go/ast"
"go/token"
"strings"
)
// DISCLAIMER: will probably not fix everything. Notable omissions:
// - conn.State will not be removed, if you were using it.
// - conn.ER/ED will not be removed; exposing them was a Bad Idea, and
// the required changes are quite a challenge to express programatically.
func init() {
register(goircFix)
}
var goircFix = fix{
"goirc",
"2013-03-24",
goircNewApi,
`Update code that uses goirc/client to new API.`,
}
const (
clientPath = "github.com/fluffle/goirc/client"
statePath = "github.com/fluffle/goirc/state"
)
var goircConstants = map[string]string{
`"REGISTER"`: "REGISTER",
`"CONNECTED"`: "CONNECTED",
`"DISCONNECTED"`: "DISCONNECTED",
`"ACTION"`: "ACTION",
`"AWAY"`: "AWAY",
`"CTCP"`: "CTCP",
`"CTCPREPLY"`: "CTCPREPLY",
`"INVITE"`: "INVITE",
`"JOIN"`: "JOIN",
`"KICK"`: "KICK",
`"MODE"`: "MODE",
`"NICK"`: "NICK",
`"NOTICE"`: "NOTICE",
`"OPER"`: "OPER",
`"PART"`: "PART",
`"PASS"`: "PASS",
`"PING"`: "PING",
`"PONG"`: "PONG",
`"PRIVMSG"`: "PRIVMSG",
`"QUIT"`: "QUIT",
`"TOPIC"`: "TOPIC",
`"USER"`: "USER",
`"VERSION"`: "VERSION",
`"VHOST"`: "VHOST",
`"WHO"`: "WHO",
`"WHOIS"`: "WHOIS",
}
var goircStructToConfig = map[string]string{
"Host": "Server",
"Network": "Server",
"NewNick": "NewNick",
"SSL": "SSL",
"SSLConfig": "SSLConfig",
"PingFreq": "PingFreq",
"Flood": "Flood",
}
var goircStructToMethod = map[string]string{
"Me": "Me",
"ST": "StateTracker",
"Connected": "Connected",
}
var goircMethodRename = map[string]string{
"AddHandler": "HandleFunc",
"Connect": "ConnectTo",
}
func addCall(t ast.Expr, method string) *ast.CallExpr {
return &ast.CallExpr{
Fun: &ast.SelectorExpr{
X: t,
Sel: ast.NewIdent(method),
},
}
}
func goircNewApi(f *ast.File) bool {
state := stateApi(f)
client := clientApi(f)
return client || state
}
func stateApi(f *ast.File) bool {
spec := importSpec(f, statePath)
if spec == nil {
return false
}
stateImport := "state"
if spec.Name != nil {
stateImport = spec.Name.Name
}
return renameFixTab(f, []rename{
{"github.com/fluffle/goirc/state", "",
stateImport + ".StateTracker", stateImport + ".Tracker"},
{"github.com/fluffle/goirc/state", "",
stateImport + ".MockStateTracker", stateImport + ".MockTracker"},
})
}
func clientApi(f *ast.File) bool {
spec := importSpec(f, clientPath)
if spec == nil {
return false
}
clientImport := "client"
if spec.Name != nil {
clientImport = spec.Name.Name
}
fixed := false
maybeReplaceBasicLit := func (expr *ast.Expr) {
str, ok := (*expr).(*ast.BasicLit)
if !ok || str == nil || str.Kind != token.STRING { return }
if repl, ok := goircConstants[strings.ToUpper(str.Value)]; ok {
*expr = &ast.SelectorExpr{
ast.NewIdent(clientImport),
ast.NewIdent(repl),
}
fixed = true
}
}
maybeReplaceConnSelectors := func (expr *ast.Expr) {
sel, ok := (*expr).(*ast.SelectorExpr)
if !ok || !isClientConn(sel.X, clientImport) { return }
name := sel.Sel.String()
if rep, ok := goircStructToConfig[name]; ok {
sel.X = addCall(sel.X, "Config")
sel.Sel = ast.NewIdent(rep)
fixed = true
} else if meth, ok := goircStructToMethod[name]; ok {
*expr = addCall(sel.X, meth)
fixed = true
} else if meth, ok := goircMethodRename[name]; ok {
sel.Sel = ast.NewIdent(meth)
fixed = true
}
}
walk(f, func(n interface{}) {
if expr, ok := n.(*ast.Expr); ok {
maybeReplaceBasicLit(expr)
maybeReplaceConnSelectors(expr)
}
if expr, ok := n.(*ast.CallExpr); ok {
if sel, ok := n.(*ast.SelectorExpr); ok &&
isPkgDot(sel, clientImport, "Client") {
// s/Client/SimpleClient/
sel.Sel = ast.NewIdent("SimpleClient")
// and delete the last arg from args
expr.Args = expr.Args[:3]
fixed = true
}
}
})
return fixed
}
func isClientConn(t ast.Expr, pkg string) bool {
// TODO(fluffle): when Conn is a struct member and we're looking for e.g.
// struct.Conn.AddHandler()
// we will pass in the *ast.SelectorExpr{X: struct, Sel: Conn} to this.
// Unfortunately the *ast.Ident{Conn} in this case often has no *ast.Object
// associated with it, so to divine it's type we need to recurse down until
// X is an *ast.Ident instead of another *ast.SelectorExpr, then look for
// the struct member types all the way back up until we get to "Conn".
// This is a massive pain-in-the-arse; I can see why type checking of this
// sort is often not done, s/AddHandler/HandleFunc/g is much simpler.
id, ok := t.(*ast.Ident)
if !ok || id.Obj == nil { return false }
switch dec := id.Obj.Decl.(type) {
case *ast.ValueSpec:
// Declared with var X Type
return dec.Type != nil && isPtrPkgDot(dec.Type, pkg, "Conn")
case *ast.AssignStmt:
// Declared with X := Expr producing Type
// NOTE: not taking care of multiple-assignment case atm!
switch rhs := dec.Rhs[0].(type) {
case *ast.CallExpr:
// X := client.Client() or client.SimpleClient()
return isPkgDot(rhs.Fun, pkg, "Client") ||
isPkgDot(rhs.Fun, pkg, "SimpleClient")
case *ast.UnaryExpr:
// X := &client.Conn{}
lit, ok := rhs.X.(*ast.CompositeLit)
return ok && isPkgDot(lit.Type, pkg, "Conn")
case *ast.CompositeLit:
// X := client.Conn{}
return isPkgDot(rhs.Type, pkg, "Conn")
default:
fmt.Printf("rhs: %#v\n", rhs)
}
case *ast.Field:
// Declared with func f(X Type)
return isPkgDot(dec.Type, pkg, "Conn")
default:
fmt.Printf("dec: %#v\n", dec)
}
return false
}