1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
|
package client
import (
"runtime"
"strings"
"sync"
"github.com/fluffle/goirc/logging"
)
// Handlers are triggered on incoming Lines from the server, with the handler
// "name" being equivalent to Line.Cmd. Read the RFCs for details on what
// replies could come from the server. They'll generally be things like
// "PRIVMSG", "JOIN", etc. but all the numeric replies are left as ascii
// strings of digits like "332" (mainly because I really didn't feel like
// putting massive constant tables in).
//
// Foreground handlers have a guarantee of protocol consistency: all the
// handlers for one event will have finished before the handlers for the
// next start processing. They are run in parallel but block the event
// loop, so care should be taken to ensure these handlers are quick :-)
//
// Background handlers are run in parallel and do not block the event loop.
// This is useful for things that may need to do significant work.
type Handler interface {
Handle(*Conn, *Line)
}
// Removers allow for a handler that has been previously added to the client
// to be removed.
type Remover interface {
Remove()
}
// HandlerFunc allows a bare function with this signature to implement the
// Handler interface. It is used by Conn.HandleFunc.
type HandlerFunc func(*Conn, *Line)
func (hf HandlerFunc) Handle(conn *Conn, line *Line) {
hf(conn, line)
}
// Handlers are organised using a map of linked-lists, with each map
// key representing an IRC verb or numeric, and the linked list values
// being handlers that are executed in parallel when a Line from the
// server with that verb or numeric arrives.
type hSet struct {
set map[string]*hList
sync.RWMutex
}
type hList struct {
start, end *hNode
}
// Storing the forward and backward links in the node allows O(1) removal.
// This probably isn't strictly necessary but I think it's kinda nice.
type hNode struct {
next, prev *hNode
set *hSet
event string
handler Handler
}
// A hNode implements both Handler (with configurable panic recovery)...
func (hn *hNode) Handle(conn *Conn, line *Line) {
defer conn.cfg.Recover(conn, line)
hn.handler.Handle(conn, line)
}
// ... and Remover.
func (hn *hNode) Remove() {
hn.set.remove(hn)
}
func handlerSet() *hSet {
return &hSet{set: make(map[string]*hList)}
}
// When a new Handler is added for an event, it is wrapped in a hNode and
// returned as a Remover so the caller can remove it at a later time.
func (hs *hSet) add(ev string, h Handler) Remover {
hs.Lock()
defer hs.Unlock()
ev = strings.ToLower(ev)
l, ok := hs.set[ev]
if !ok {
l = &hList{}
}
hn := &hNode{
set: hs,
event: ev,
handler: h,
}
if !ok {
l.start = hn
} else {
hn.prev = l.end
l.end.next = hn
}
l.end = hn
hs.set[ev] = l
return hn
}
func (hs *hSet) remove(hn *hNode) {
hs.Lock()
defer hs.Unlock()
l, ok := hs.set[hn.event]
if !ok {
logging.Error("Removing node for unknown event '%s'", hn.event)
return
}
if hn.next == nil {
l.end = hn.prev
} else {
hn.next.prev = hn.prev
}
if hn.prev == nil {
l.start = hn.next
} else {
hn.prev.next = hn.next
}
hn.next = nil
hn.prev = nil
hn.set = nil
if l.start == nil || l.end == nil {
delete(hs.set, hn.event)
}
}
func (hs *hSet) getHandlers(ev string) []*hNode {
hs.RLock()
defer hs.RUnlock()
list, ok := hs.set[ev]
if !ok {
return nil
}
// Copy current list of handlers to a temporary slice under the lock.
handlers := make([]*hNode, 0)
for hn := list.start; hn != nil; hn = hn.next {
handlers = append(handlers, hn)
}
return handlers
}
func (hs *hSet) dispatch(conn *Conn, line *Line) {
ev := strings.ToLower(line.Cmd)
wg := &sync.WaitGroup{}
for _, hn := range hs.getHandlers(ev) {
wg.Add(1)
go func(hn *hNode) {
hn.Handle(conn, line.Copy())
wg.Done()
}(hn)
}
wg.Wait()
}
// Handle adds the provided handler to the foreground set for the named event.
// It will return a Remover that allows that handler to be removed again.
func (conn *Conn) Handle(name string, h Handler) Remover {
return conn.fgHandlers.add(name, h)
}
// HandleBG adds the provided handler to the background set for the named
// event. It may go away in the future.
// It will return a Remover that allows that handler to be removed again.
func (conn *Conn) HandleBG(name string, h Handler) Remover {
return conn.bgHandlers.add(name, h)
}
func (conn *Conn) handle(name string, h Handler) Remover {
return conn.intHandlers.add(name, h)
}
// HandleFunc adds the provided function as a handler in the foreground set
// for the named event.
// It will return a Remover that allows that handler to be removed again.
func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover {
return conn.Handle(name, hf)
}
func (conn *Conn) dispatch(line *Line) {
// We run the internal handlers first, including all state tracking ones.
// This ensures that user-supplied handlers that use the tracker have a
// consistent view of the connection state in handlers that mutate it.
conn.intHandlers.dispatch(conn, line)
go conn.bgHandlers.dispatch(conn, line)
conn.fgHandlers.dispatch(conn, line)
}
// LogPanic is used as the default panic catcher for the client. If, like me,
// you are not good with computer, and you'd prefer your bot not to vanish into
// the ether whenever you make unfortunate programming mistakes, you may find
// this useful: it will recover panics from handler code and log the errors.
func (conn *Conn) LogPanic(line *Line) {
if err := recover(); err != nil {
_, f, l, _ := runtime.Caller(2)
logging.Error("%s:%d: panic: %v", f, l, err)
}
}
|