mirror of https://github.com/fluffle/goirc
Merge remote-tracking branch 'upstream/master'
Conflicts: client/connection.go
This commit is contained in:
commit
d1aa016bb0
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()`.
|
|
||||||
|
|
213
fix/goirc.go
213
fix/goirc.go
|
@ -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
|
|
||||||
}
|
|
Loading…
Reference in New Issue