gorepost/irc/irc.go

213 lines
5.3 KiB
Go
Raw Normal View History

2015-11-12 09:07:53 +00:00
// Copyright 2015 Robert S. Gerus. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package irc
import (
"bufio"
"log"
2015-11-07 11:51:45 +00:00
"math/rand"
"net"
2015-11-07 11:51:45 +00:00
"sync"
"time"
2015-11-10 20:57:31 +00:00
2015-12-14 13:58:42 +00:00
"github.com/arachnist/dyncfg"
)
const delim byte = '\n'
2015-10-20 08:45:50 +00:00
const endline string = "\r\n"
// Connection struct. Contains basic information about this connection, and quit
// channels.
type Connection struct {
network string
reader *bufio.Reader
writer *bufio.Writer
dispatcher func(func(Message), Message)
conn net.Conn
reconnect chan struct{}
reconnectCleanup chan struct{}
Quit chan struct{}
quitrecv chan struct{}
quitkeeper chan struct{}
l sync.Mutex
2015-12-14 13:58:42 +00:00
cfg *dyncfg.Dyncfg
}
// Sender sends IRC messages to server and logs their contents.
func (c *Connection) Sender(msg Message) {
c.l.Lock()
defer c.l.Unlock()
c.writer.WriteString(msg.String() + endline)
log.Println(c.network, "-->", msg.String())
c.writer.Flush()
}
// Receiver receives IRC messages from server, logs their contents, sets message
// context and initializes disconnect procedure on timeout or other errors.
2015-11-07 02:28:34 +00:00
func (c *Connection) Receiver() {
2015-11-10 20:57:31 +00:00
log.Println(c.network, "spawned Receiver")
for {
c.conn.SetDeadline(time.Now().Add(time.Second * 600))
raw, err := c.reader.ReadString(delim)
var src, tgt string
if err != nil {
2015-11-10 20:57:31 +00:00
log.Println(c.network, "error reading message", err.Error())
log.Println(c.network, "closing Receiver")
c.reconnectCleanup <- struct{}{}
log.Println(c.network, "sent reconnect message from Receiver")
2015-11-07 11:51:45 +00:00
return
}
msg, err := ParseMessage(raw)
if err != nil {
2015-11-10 20:57:31 +00:00
log.Println(c.network, "error decoding message", err.Error())
log.Println(c.network, "closing Receiver")
c.reconnectCleanup <- struct{}{}
log.Println(c.network, "sent reconnect message from Receiver")
2015-11-07 11:51:45 +00:00
return
}
log.Println(c.network, "<--", msg.String())
if msg.Params == nil {
tgt = ""
} else {
tgt = msg.Params[0]
}
if msg.Prefix == nil {
src = ""
} else {
src = msg.Prefix.Name
}
msg.Context = map[string]string{
2015-11-10 20:57:31 +00:00
"Network": c.network,
"Source": src,
"Target": tgt,
}
c.dispatcher(c.Sender, *msg)
select {
case <-c.quitrecv:
2015-11-10 20:57:31 +00:00
log.Println(c.network, "closing Receiver")
return
default:
}
}
}
// Cleaner cleans up coroutines on IRC connection errors and initializes
// reconnection.
2015-11-07 11:51:45 +00:00
func (c *Connection) Cleaner() {
2015-11-10 20:57:31 +00:00
log.Println(c.network, "spawned Cleaner")
2015-11-07 11:51:45 +00:00
for {
select {
case <-c.Quit:
log.Println(c.network, "closing connection")
c.l.Lock()
defer c.l.Unlock()
log.Println(c.network, "cleaning up!")
c.quitrecv <- struct{}{}
2015-12-09 09:23:54 +00:00
// there's a slight chance to hit this if quit request is received
// before irc connection is established, possibly between reconnects
if c.conn != nil {
c.conn.Close()
}
log.Println(c.network, "closing Cleaner")
return
case <-c.reconnectCleanup:
log.Println(c.network, "cleaning up before reconnect!")
c.l.Lock()
log.Println(c.network, "cleaning up!")
c.quitrecv <- struct{}{}
c.conn.Close()
log.Println(c.network, "sending reconnect signal!")
c.l.Unlock()
c.reconnect <- struct{}{}
}
2015-11-07 11:51:45 +00:00
}
}
// Keeper makes sure that IRC connection is alive by reconnecting when
// requested and restarting Receiver goroutine.
2015-11-10 20:57:31 +00:00
func (c *Connection) Keeper() {
log.Println(c.network, "spawned Keeper")
context := make(map[string]string)
context["Network"] = c.network
2015-11-07 11:51:45 +00:00
for {
select {
case <-c.quitkeeper:
if c.quitrecv != nil {
close(c.quitrecv)
}
return
case <-c.reconnect:
}
c.l.Lock()
if c.quitrecv != nil {
close(c.quitrecv)
}
c.quitrecv = make(chan struct{}, 1)
servers := c.cfg.LookupStringSlice(context, "Servers")
2015-11-10 20:57:31 +00:00
2015-11-18 13:04:56 +00:00
server := servers[rand.Intn(len(servers))]
2015-11-10 20:57:31 +00:00
log.Println(c.network, "connecting to", server)
2015-11-08 23:43:34 +00:00
err := c.Dial(server)
c.l.Unlock()
2015-11-08 23:43:34 +00:00
if err == nil {
go c.Receiver()
2015-11-10 20:57:31 +00:00
log.Println(c.network, "Initializing IRC connection")
c.Sender(Message{
2015-11-08 23:43:34 +00:00
Command: "NICK",
Trailing: c.cfg.LookupString(context, "Nick"),
})
c.Sender(Message{
2015-11-08 23:43:34 +00:00
Command: "USER",
Params: []string{c.cfg.LookupString(context, "User"), "0", "*"},
Trailing: c.cfg.LookupString(context, "RealName"),
})
2015-11-08 23:43:34 +00:00
} else {
2015-11-10 20:57:31 +00:00
log.Println(c.network, "connection error", err.Error())
2015-11-29 20:21:56 +00:00
time.Sleep(time.Second * 3)
2015-11-08 23:43:34 +00:00
c.reconnect <- struct{}{}
2015-11-07 11:51:45 +00:00
}
2015-10-20 09:06:58 +00:00
}
}
// Setup performs initialization tasks.
2015-12-14 13:58:42 +00:00
func (c *Connection) Setup(dispatcher func(func(Message), Message), network string, config *dyncfg.Dyncfg) {
2015-11-07 11:51:45 +00:00
rand.Seed(time.Now().UnixNano())
c.reconnect = make(chan struct{}, 1)
c.reconnectCleanup = make(chan struct{}, 1)
c.quitkeeper = make(chan struct{}, 1)
2015-11-07 11:51:45 +00:00
c.Quit = make(chan struct{}, 1)
2015-11-10 20:57:31 +00:00
c.network = network
2015-11-08 21:00:33 +00:00
c.dispatcher = dispatcher
c.cfg = config
2015-11-07 11:51:45 +00:00
c.reconnect <- struct{}{}
2015-11-10 20:57:31 +00:00
go c.Keeper()
2015-11-07 11:51:45 +00:00
go c.Cleaner()
return
}
// Dial connects to irc server and sets up bufio reader and writer.
2015-11-07 11:51:45 +00:00
func (c *Connection) Dial(server string) error {
conn, err := net.DialTimeout("tcp", server, time.Second*30)
if err != nil {
2015-11-10 20:57:31 +00:00
log.Println(c.network, "Cannot connect to", server, "error:", err.Error())
return err
}
2015-11-10 20:57:31 +00:00
log.Println(c.network, "Connected to", server)
c.writer = bufio.NewWriter(conn)
c.reader = bufio.NewReader(conn)
2015-11-07 11:51:45 +00:00
c.conn = conn
return nil
}