mirror of
				https://github.com/fluffle/goirc
				synced 2025-11-04 03:58:03 +00:00 
			
		
		
		
	The great state tracker privatisation 2/3: nicks.
This commit is contained in:
		
							parent
							
								
									bffe946388
								
							
						
					
					
						commit
						4dd8bc72d5
					
				
					 2 changed files with 101 additions and 66 deletions
				
			
		
							
								
								
									
										128
									
								
								state/nick.go
									
										
									
									
									
								
							
							
						
						
									
										128
									
								
								state/nick.go
									
										
									
									
									
								
							| 
						 | 
					@ -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 {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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.")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue