The great state tracker privatisation 2/3: nicks.

This commit is contained in:
Alex Bramley 2014-12-31 13:17:28 +00:00
parent bffe946388
commit 4dd8bc72d5
2 changed files with 101 additions and 66 deletions

View File

@ -4,15 +4,22 @@ import (
"github.com/fluffle/goirc/logging" "github.com/fluffle/goirc/logging"
"reflect" "reflect"
"sort"
) )
// A struct representing an IRC nick // A Nick is returned from the state tracker and contains
// a copy of the nick state at a particular time.
type Nick struct { type Nick struct {
Nick, Ident, Host, Name string Nick, Ident, Host, Name string
Modes *NickMode Modes *NickMode
lookup map[string]*Channel Channels map[string]*ChanPrivs
chans map[*Channel]*ChanPrivs }
// Internal bookkeeping struct for nicks.
type nick struct {
nick, ident, host, name string
modes *NickMode
lookup map[string]*channel
chans map[*channel]*ChanPrivs
} }
// A struct representing the modes of an IRC Nick (User Modes) // A struct representing the modes of an IRC Nick (User Modes)
@ -43,51 +50,62 @@ func init() {
} }
/******************************************************************************\ /******************************************************************************\
* Nick methods for state management * nick methods for state management
\******************************************************************************/ \******************************************************************************/
func NewNick(n string) *Nick { func newNick(n string) *nick {
return &Nick{ return &nick{
Nick: n, nick: n,
Modes: new(NickMode), modes: new(NickMode),
chans: make(map[*Channel]*ChanPrivs), chans: make(map[*channel]*ChanPrivs),
lookup: make(map[string]*Channel), lookup: make(map[string]*channel),
} }
} }
// Returns true if the Nick is associated with the Channel. // Returns a copy of the internal tracker nick state at this time.
func (nk *Nick) IsOn(ch *Channel) (*ChanPrivs, bool) { // Relies on tracker-level locking for concurrent access.
cp, ok := nk.chans[ch] func (nk *nick) Nick() *Nick {
return cp, ok n := &Nick{
Nick: nk.nick,
Ident: nk.ident,
Host: nk.host,
Name: nk.name,
Modes: nk.modes.Copy(),
Channels: make(map[string]*ChanPrivs),
}
for c, cp := range nk.chans {
n.Channels[c.name] = cp.Copy()
}
return n
} }
func (nk *Nick) IsOnStr(c string) (*Channel, bool) { func (nk *nick) isOn(ch *channel) (*ChanPrivs, bool) {
ch, ok := nk.lookup[c] cp, ok := nk.chans[ch]
return ch, ok return cp.Copy(), ok
} }
// Associates a Channel with a Nick. // Associates a Channel with a Nick.
func (nk *Nick) addChannel(ch *Channel, cp *ChanPrivs) { func (nk *nick) addChannel(ch *channel, cp *ChanPrivs) {
if _, ok := nk.chans[ch]; !ok { if _, ok := nk.chans[ch]; !ok {
nk.chans[ch] = cp nk.chans[ch] = cp
nk.lookup[ch.Name] = ch nk.lookup[ch.name] = ch
} else { } else {
logging.Warn("Nick.addChannel(): %s already on %s.", nk.Nick, ch.Name) logging.Warn("Nick.addChannel(): %s already on %s.", nk.nick, ch.name)
} }
} }
// Disassociates a Channel from a Nick. // Disassociates a Channel from a Nick.
func (nk *Nick) delChannel(ch *Channel) { func (nk *nick) delChannel(ch *channel) {
if _, ok := nk.chans[ch]; ok { if _, ok := nk.chans[ch]; ok {
delete(nk.chans, ch) delete(nk.chans, ch)
delete(nk.lookup, ch.Name) delete(nk.lookup, ch.name)
} else { } else {
logging.Warn("Nick.delChannel(): %s not on %s.", nk.Nick, ch.Name) logging.Warn("Nick.delChannel(): %s not on %s.", nk.nick, ch.name)
} }
} }
// Parse mode strings for a Nick. // Parse mode strings for a Nick.
func (nk *Nick) ParseModes(modes string) { func (nk *nick) parseModes(modes string) {
var modeop bool // true => add mode, false => remove mode var modeop bool // true => add mode, false => remove mode
for i := 0; i < len(modes); i++ { for i := 0; i < len(modes); i++ {
switch m := modes[i]; m { switch m := modes[i]; m {
@ -96,46 +114,44 @@ func (nk *Nick) ParseModes(modes string) {
case '-': case '-':
modeop = false modeop = false
case 'B': case 'B':
nk.Modes.Bot = modeop nk.modes.Bot = modeop
case 'i': case 'i':
nk.Modes.Invisible = modeop nk.modes.Invisible = modeop
case 'o': case 'o':
nk.Modes.Oper = modeop nk.modes.Oper = modeop
case 'w': case 'w':
nk.Modes.WallOps = modeop nk.modes.WallOps = modeop
case 'x': case 'x':
nk.Modes.HiddenHost = modeop nk.modes.HiddenHost = modeop
case 'z': case 'z':
nk.Modes.SSL = modeop nk.modes.SSL = modeop
default: default:
logging.Info("Nick.ParseModes(): unknown mode char %c", m) logging.Info("Nick.ParseModes(): unknown mode char %c", m)
} }
} }
} }
type byName []*Channel // Returns true if the Nick is associated with the Channel.
func (nk *Nick) IsOn(ch string) (*ChanPrivs, bool) {
func (b byName) Len() int { return len(b) } cp, ok := nk.Channels[ch]
func (b byName) Less(i, j int) bool { return b[i].Name < b[j].Name } return cp, ok
func (b byName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
// Channels returns a list of *Channel the nick is on, sorted by name.
func (nk *Nick) Channels() []*Channel {
channels := make([]*Channel, 0, len(nk.lookup))
for _, channel := range nk.lookup {
channels = append(channels, channel)
}
sort.Sort(byName(channels))
return channels
} }
// ChannelsStr returns a list of channel strings the nick is on, sorted by name. // Tests Nick equality.
func (nk *Nick) ChannelsStr() []string { func (nk *Nick) Equals(other *Nick) bool {
var names []string return reflect.DeepEqual(nk, other)
for _, channel := range nk.Channels() { }
names = append(names, channel.Name)
} // Duplicates a NickMode struct.
return names func (nm *NickMode) Copy() *NickMode {
if nm == nil { return nil }
n := *nm
return &n
}
// Tests NickMode equality.
func (nm *NickMode) Equals(other *NickMode) bool {
return reflect.DeepEqual(nm, other)
} }
// Returns a string representing the nick. Looks like: // Returns a string representing the nick. Looks like:
@ -152,12 +168,16 @@ func (nk *Nick) String() string {
str += "Real Name: " + nk.Name + "\n\t" str += "Real Name: " + nk.Name + "\n\t"
str += "Modes: " + nk.Modes.String() + "\n\t" str += "Modes: " + nk.Modes.String() + "\n\t"
str += "Channels: \n" str += "Channels: \n"
for ch, cp := range nk.chans { for ch, cp := range nk.Channels {
str += "\t\t" + ch.Name + ": " + cp.String() + "\n" str += "\t\t" + ch + ": " + cp.String() + "\n"
} }
return str return str
} }
func (nk *nick) String() string {
return nk.Nick().String()
}
// Returns a string representing the nick modes. Looks like: // Returns a string representing the nick modes. Looks like:
// +iwx // +iwx
func (nm *NickMode) String() string { func (nm *NickMode) String() string {

View File

@ -1,23 +1,35 @@
package state package state
import ( import "testing"
"testing"
) func compareNick(t *testing.T, nk *nick) {
n := nk.Nick()
if n.Nick != nk.nick || n.Ident != nk.ident || n.Host != nk.host || n.Name != nk.name ||
!n.Modes.Equals(nk.modes) || len(n.Channels) != len(nk.chans) {
t.Errorf("Nick not duped correctly from internal state.")
}
for ch, cp := range nk.chans {
if other, ok := n.Channels[ch.name]; !ok || !cp.Equals(other) {
t.Errorf("Channel not duped correctly from internal state.")
}
}
}
func TestNewNick(t *testing.T) { func TestNewNick(t *testing.T) {
nk := NewNick("test1") nk := newNick("test1")
if nk.Nick != "test1" { if nk.nick != "test1" {
t.Errorf("Nick not created correctly by NewNick()") t.Errorf("Nick not created correctly by NewNick()")
} }
if len(nk.chans) != 0 || len(nk.lookup) != 0 { if len(nk.chans) != 0 || len(nk.lookup) != 0 {
t.Errorf("Nick maps contain data after NewNick()") t.Errorf("Nick maps contain data after NewNick()")
} }
compareNick(t, nk)
} }
func TestAddChannel(t *testing.T) { func TestAddChannel(t *testing.T) {
nk := NewNick("test1") nk := newNick("test1")
ch := NewChannel("#test1") ch := newChannel("#test1")
cp := new(ChanPrivs) cp := new(ChanPrivs)
nk.addChannel(ch, cp) nk.addChannel(ch, cp)
@ -31,11 +43,12 @@ func TestAddChannel(t *testing.T) {
if c, ok := nk.lookup["#test1"]; !ok || c != ch { if c, ok := nk.lookup["#test1"]; !ok || c != ch {
t.Errorf("Channel #test1 not properly stored in lookup map.") t.Errorf("Channel #test1 not properly stored in lookup map.")
} }
compareNick(t, nk)
} }
func TestDelChannel(t *testing.T) { func TestDelChannel(t *testing.T) {
nk := NewNick("test1") nk := newNick("test1")
ch := NewChannel("#test1") ch := newChannel("#test1")
cp := new(ChanPrivs) cp := new(ChanPrivs)
nk.addChannel(ch, cp) nk.addChannel(ch, cp)
@ -49,11 +62,12 @@ func TestDelChannel(t *testing.T) {
if c, ok := nk.lookup["#test1"]; ok || c != nil { if c, ok := nk.lookup["#test1"]; ok || c != nil {
t.Errorf("Channel #test1 not properly removed from lookup map.") t.Errorf("Channel #test1 not properly removed from lookup map.")
} }
compareNick(t, nk)
} }
func TestNickParseModes(t *testing.T) { func TestNickParseModes(t *testing.T) {
nk := NewNick("test1") nk := newNick("test1")
md := nk.Modes md := nk.modes
// Modes should all be false for a new nick // Modes should all be false for a new nick
if md.Invisible || md.Oper || md.WallOps || md.HiddenHost || md.SSL { if md.Invisible || md.Oper || md.WallOps || md.HiddenHost || md.SSL {
@ -65,8 +79,9 @@ func TestNickParseModes(t *testing.T) {
md.HiddenHost = true md.HiddenHost = true
// Parse a mode line that flips one true to false and two false to true // Parse a mode line that flips one true to false and two false to true
nk.ParseModes("+z-x+w") nk.parseModes("+z-x+w")
compareNick(t, nk)
if !md.Invisible || md.Oper || !md.WallOps || md.HiddenHost || !md.SSL { if !md.Invisible || md.Oper || !md.WallOps || md.HiddenHost || !md.SSL {
t.Errorf("Modes not flipped correctly by ParseModes.") t.Errorf("Modes not flipped correctly by ParseModes.")
} }