summaryrefslogtreecommitdiff
path: root/vendor/github.com/fluffle/goirc/state/tracker.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/fluffle/goirc/state/tracker.go')
-rw-r--r--vendor/github.com/fluffle/goirc/state/tracker.go369
1 files changed, 369 insertions, 0 deletions
diff --git a/vendor/github.com/fluffle/goirc/state/tracker.go b/vendor/github.com/fluffle/goirc/state/tracker.go
new file mode 100644
index 0000000..209c7cf
--- /dev/null
+++ b/vendor/github.com/fluffle/goirc/state/tracker.go
@@ -0,0 +1,369 @@
+package state
+
+import (
+ "github.com/fluffle/goirc/logging"
+
+ "sync"
+)
+
+// The state manager interface
+type Tracker interface {
+ // Nick methods
+ NewNick(nick string) *Nick
+ GetNick(nick string) *Nick
+ ReNick(old, neu string) *Nick
+ DelNick(nick string) *Nick
+ NickInfo(nick, ident, host, name string) *Nick
+ NickModes(nick, modestr string) *Nick
+ // Channel methods
+ NewChannel(channel string) *Channel
+ GetChannel(channel string) *Channel
+ DelChannel(channel string) *Channel
+ Topic(channel, topic string) *Channel
+ ChannelModes(channel, modestr string, modeargs ...string) *Channel
+ // Information about ME!
+ Me() *Nick
+ // And the tracking operations
+ IsOn(channel, nick string) (*ChanPrivs, bool)
+ Associate(channel, nick string) *ChanPrivs
+ Dissociate(channel, nick string)
+ Wipe()
+ // The state tracker can output a debugging string
+ String() string
+}
+
+// ... and a struct to implement it ...
+type stateTracker struct {
+ // Map of channels we're on
+ chans map[string]*channel
+ // Map of nicks we know about
+ nicks map[string]*nick
+
+ // We need to keep state on who we are :-)
+ me *nick
+
+ // And we need to protect against data races *cough*.
+ mu sync.Mutex
+}
+
+var _ Tracker = (*stateTracker)(nil)
+
+// ... and a constructor to make it ...
+func NewTracker(mynick string) *stateTracker {
+ st := &stateTracker{
+ chans: make(map[string]*channel),
+ nicks: make(map[string]*nick),
+ }
+ st.me = newNick(mynick)
+ st.nicks[mynick] = st.me
+ return st
+}
+
+// ... and a method to wipe the state clean.
+func (st *stateTracker) Wipe() {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ // Deleting all the channels implicitly deletes every nick but me.
+ for _, ch := range st.chans {
+ st.delChannel(ch)
+ }
+}
+
+/******************************************************************************\
+ * tracker methods to create/look up nicks/channels
+\******************************************************************************/
+
+// Creates a new nick, initialises it, and stores it so it
+// can be properly tracked for state management purposes.
+func (st *stateTracker) NewNick(n string) *Nick {
+ if n == "" {
+ logging.Warn("Tracker.NewNick(): Not tracking empty nick.")
+ return nil
+ }
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ if _, ok := st.nicks[n]; ok {
+ logging.Warn("Tracker.NewNick(): %s already tracked.", n)
+ return nil
+ }
+ st.nicks[n] = newNick(n)
+ return st.nicks[n].Nick()
+}
+
+// Returns a nick for the nick n, if we're tracking it.
+func (st *stateTracker) GetNick(n string) *Nick {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ if nk, ok := st.nicks[n]; ok {
+ return nk.Nick()
+ }
+ return nil
+}
+
+// Signals to the tracker that a nick should be tracked
+// under a "neu" nick rather than the old one.
+func (st *stateTracker) ReNick(old, neu string) *Nick {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ nk, ok := st.nicks[old]
+ if !ok {
+ logging.Warn("Tracker.ReNick(): %s not tracked.", old)
+ return nil
+ }
+ if _, ok := st.nicks[neu]; ok {
+ logging.Warn("Tracker.ReNick(): %s already exists.", neu)
+ return nil
+ }
+
+ nk.nick = neu
+ delete(st.nicks, old)
+ st.nicks[neu] = nk
+ for ch, _ := range nk.chans {
+ // We also need to update the lookup maps of all the channels
+ // the nick is on, to keep things in sync.
+ delete(ch.lookup, old)
+ ch.lookup[neu] = nk
+ }
+ return nk.Nick()
+}
+
+// Removes a nick from being tracked.
+func (st *stateTracker) DelNick(n string) *Nick {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ if nk, ok := st.nicks[n]; ok {
+ if nk == st.me {
+ logging.Warn("Tracker.DelNick(): won't delete myself.")
+ return nil
+ }
+ st.delNick(nk)
+ return nk.Nick()
+ }
+ logging.Warn("Tracker.DelNick(): %s not tracked.", n)
+ return nil
+}
+
+func (st *stateTracker) delNick(nk *nick) {
+ // st.mu lock held by DelNick, DelChannel or Wipe
+ if nk == st.me {
+ // Shouldn't get here => internal state tracking code is fubar.
+ logging.Error("Tracker.DelNick(): TRYING TO DELETE ME :-(")
+ return
+ }
+ delete(st.nicks, nk.nick)
+ for ch, _ := range nk.chans {
+ nk.delChannel(ch)
+ ch.delNick(nk)
+ if len(ch.nicks) == 0 {
+ // Deleting a nick from tracking shouldn't empty any channels as
+ // *we* should be on the channel with them to be tracking them.
+ logging.Error("Tracker.delNick(): deleting nick %s emptied "+
+ "channel %s, this shouldn't happen!", nk.nick, ch.name)
+ }
+ }
+}
+
+// Sets ident, host and "real" name for the nick.
+func (st *stateTracker) NickInfo(n, ident, host, name string) *Nick {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ nk, ok := st.nicks[n]
+ if !ok {
+ return nil
+ }
+ nk.ident = ident
+ nk.host = host
+ nk.name = name
+ return nk.Nick()
+}
+
+// Sets user modes for the nick.
+func (st *stateTracker) NickModes(n, modes string) *Nick {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ nk, ok := st.nicks[n]
+ if !ok {
+ return nil
+ }
+ nk.parseModes(modes)
+ return nk.Nick()
+}
+
+// Creates a new Channel, initialises it, and stores it so it
+// can be properly tracked for state management purposes.
+func (st *stateTracker) NewChannel(c string) *Channel {
+ if c == "" {
+ logging.Warn("Tracker.NewChannel(): Not tracking empty channel.")
+ return nil
+ }
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ if _, ok := st.chans[c]; ok {
+ logging.Warn("Tracker.NewChannel(): %s already tracked.", c)
+ return nil
+ }
+ st.chans[c] = newChannel(c)
+ return st.chans[c].Channel()
+}
+
+// Returns a Channel for the channel c, if we're tracking it.
+func (st *stateTracker) GetChannel(c string) *Channel {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ if ch, ok := st.chans[c]; ok {
+ return ch.Channel()
+ }
+ return nil
+}
+
+// Removes a Channel from being tracked.
+func (st *stateTracker) DelChannel(c string) *Channel {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ if ch, ok := st.chans[c]; ok {
+ st.delChannel(ch)
+ return ch.Channel()
+ }
+ logging.Warn("Tracker.DelChannel(): %s not tracked.", c)
+ return nil
+}
+
+func (st *stateTracker) delChannel(ch *channel) {
+ // st.mu lock held by DelChannel or Wipe
+ delete(st.chans, ch.name)
+ for nk, _ := range ch.nicks {
+ ch.delNick(nk)
+ nk.delChannel(ch)
+ if len(nk.chans) == 0 && nk != st.me {
+ // We're no longer in any channels with this nick.
+ st.delNick(nk)
+ }
+ }
+}
+
+// Sets the topic of a channel.
+func (st *stateTracker) Topic(c, topic string) *Channel {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ ch, ok := st.chans[c]
+ if !ok {
+ return nil
+ }
+ ch.topic = topic
+ return ch.Channel()
+}
+
+// Sets modes for a channel, including privileges like +o.
+func (st *stateTracker) ChannelModes(c, modes string, args ...string) *Channel {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ ch, ok := st.chans[c]
+ if !ok {
+ return nil
+ }
+ ch.parseModes(modes, args...)
+ return ch.Channel()
+}
+
+// Returns the Nick the state tracker thinks is Me.
+// NOTE: Nick() requires the mutex to be held.
+func (st *stateTracker) Me() *Nick {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ return st.me.Nick()
+}
+
+// Returns true if both the channel c and the nick n are tracked
+// and the nick is associated with the channel.
+func (st *stateTracker) IsOn(c, n string) (*ChanPrivs, bool) {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ nk, nok := st.nicks[n]
+ ch, cok := st.chans[c]
+ if nok && cok {
+ return nk.isOn(ch)
+ }
+ return nil, false
+}
+
+// Associates an already known nick with an already known channel.
+func (st *stateTracker) Associate(c, n string) *ChanPrivs {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ nk, nok := st.nicks[n]
+ ch, cok := st.chans[c]
+
+ if !cok {
+ // As we can implicitly delete both nicks and channels from being
+ // tracked by dissociating one from the other, we should verify that
+ // we're not being passed an old Nick or Channel.
+ logging.Error("Tracker.Associate(): channel %s not found in "+
+ "internal state.", c)
+ return nil
+ } else if !nok {
+ logging.Error("Tracker.Associate(): nick %s not found in "+
+ "internal state.", n)
+ return nil
+ } else if _, ok := nk.isOn(ch); ok {
+ logging.Warn("Tracker.Associate(): %s already on %s.",
+ nk, ch)
+ return nil
+ }
+ cp := new(ChanPrivs)
+ ch.addNick(nk, cp)
+ nk.addChannel(ch, cp)
+ return cp.Copy()
+}
+
+// Dissociates an already known nick from an already known channel.
+// Does some tidying up to stop tracking nicks we're no longer on
+// any common channels with, and channels we're no longer on.
+func (st *stateTracker) Dissociate(c, n string) {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ nk, nok := st.nicks[n]
+ ch, cok := st.chans[c]
+
+ if !cok {
+ // As we can implicitly delete both nicks and channels from being
+ // tracked by dissociating one from the other, we should verify that
+ // we're not being passed an old Nick or Channel.
+ logging.Error("Tracker.Dissociate(): channel %s not found in "+
+ "internal state.", c)
+ } else if !nok {
+ logging.Error("Tracker.Dissociate(): nick %s not found in "+
+ "internal state.", n)
+ } else if _, ok := nk.isOn(ch); !ok {
+ logging.Warn("Tracker.Dissociate(): %s not on %s.",
+ nk.nick, ch.name)
+ } else if nk == st.me {
+ // I'm leaving the channel for some reason, so it won't be tracked.
+ st.delChannel(ch)
+ } else {
+ // Remove the nick from the channel and the channel from the nick.
+ ch.delNick(nk)
+ nk.delChannel(ch)
+ if len(nk.chans) == 0 {
+ // We're no longer in any channels with this nick.
+ st.delNick(nk)
+ }
+ }
+}
+
+func (st *stateTracker) String() string {
+ st.mu.Lock()
+ defer st.mu.Unlock()
+ str := "GoIRC Channels\n"
+ str += "--------------\n\n"
+ for _, ch := range st.chans {
+ str += ch.String() + "\n"
+ }
+ str += "GoIRC NickNames\n"
+ str += "---------------\n\n"
+ for _, n := range st.nicks {
+ if n != st.me {
+ str += n.String() + "\n"
+ }
+ }
+ return str
+}