mirror of
https://github.com/fluffle/goirc
synced 2025-09-06 00:43:20 +00:00
Re-work Handlers for IRC events; add Commands.
This commit is contained in:
parent
a038856094
commit
a674267128
8 changed files with 692 additions and 158 deletions
228
client/dispatch.go
Normal file
228
client/dispatch.go
Normal file
|
@ -0,0 +1,228 @@
|
|||
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)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue