goirc/client/dispatch.go

231 lines
4.2 KiB
Go
Raw Normal View History

package client
import (
"github.com/fluffle/golog/logging"
"strings"
"sync"
)
// An IRC handler looks like this:
type Handler interface {
Handle(*Conn, *Line)
}
// And when they've been added to the client they are removable.
type Remover interface {
Remove()
}
type HandlerFunc func(*Conn, *Line)
func (hf HandlerFunc) Handle(conn *Conn, line *Line) {
hf(conn, line)
}
type hList struct {
start, end *hNode
}
type hNode struct {
next, prev *hNode
set *hSet
event string
handler Handler
}
func (hn *hNode) Handle(conn *Conn, line *Line) {
hn.handler.Handle(conn, line)
}
func (hn *hNode) Remove() {
hn.set.remove(hn)
}
type hSet struct {
set map[string]*hList
sync.RWMutex
}
func handlerSet() *hSet {
return &hSet{set: make(map[string]*hList)}
}
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) dispatch(conn *Conn, line *Line) {
hs.RLock()
defer hs.RUnlock()
ev := strings.ToLower(line.Cmd)
list, ok := hs.set[ev]
if !ok {
return
}
for hn := list.start; hn != nil; hn = hn.next {
go hn.Handle(conn, line)
}
}
// An IRC command looks like this:
type Command interface {
Execute(*Conn, *Line)
Help() string
}
type command struct {
fn HandlerFunc
help string
}
func (c *command) Execute(conn *Conn, line *Line) {
c.fn(conn, line)
}
func (c *command) Help() string {
return c.help
}
type cNode struct {
cmd Command
set *cSet
prefix string
}
func (cn *cNode) Execute(conn *Conn, line *Line) {
cn.cmd.Execute(conn, line)
}
func (cn *cNode) Help() string {
return cn.cmd.Help()
}
func (cn *cNode) Remove() {
cn.set.remove(cn)
}
type cSet struct {
set map[string]*cNode
sync.RWMutex
}
func commandSet() *cSet {
return &cSet{set: make(map[string]*cNode)}
}
func (cs *cSet) add(pf string, c Command) Remover {
cs.Lock()
defer cs.Unlock()
pf = strings.ToLower(pf)
if _, ok := cs.set[pf]; ok {
logging.Error("Command prefix '%s' already registered.", pf)
return nil
}
cn := &cNode{
cmd: c,
set: cs,
prefix: pf,
}
cs.set[pf] = cn
return cn
}
func (cs *cSet) remove(cn *cNode) {
cs.Lock()
defer cs.Unlock()
delete(cs.set, cn.prefix)
cn.set = nil
}
func (cs *cSet) match(txt string) (final Command, prefixlen int) {
cs.RLock()
defer cs.RUnlock()
txt = strings.ToLower(txt)
for prefix, cmd := range cs.set {
if !strings.HasPrefix(txt, prefix) {
continue
}
if final == nil || len(prefix) > prefixlen {
prefixlen = len(prefix)
final = cmd
}
}
return
}
// 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).
func (conn *Conn) Handle(name string, h Handler) Remover {
return conn.handlers.add(name, h)
}
func (conn *Conn) HandleFunc(name string, hf HandlerFunc) Remover {
return conn.Handle(name, hf)
}
func (conn *Conn) Command(prefix string, c Command) Remover {
return conn.commands.add(prefix, c)
}
func (conn *Conn) CommandFunc(prefix string, hf HandlerFunc, help string) Remover {
return conn.Command(prefix, &command{hf, help})
}
func (conn *Conn) dispatch(line *Line) {
conn.handlers.dispatch(conn, line)
}
func (conn *Conn) cmdMatch(txt string) (Command, int) {
return conn.commands.match(txt)
}