cleanup state mutexs, add state change events

This commit is contained in:
Liam Stanley 2017-07-12 00:44:44 -04:00
parent bd2c3e31ea
commit 9e08ab340d
9 changed files with 191 additions and 171 deletions

View File

@ -58,7 +58,7 @@ func (c *Client) registerBuiltins() {
c.Handlers.register(true, CAP_CHGHOST, HandlerFunc(handleCHGHOST))
c.Handlers.register(true, CAP_AWAY, HandlerFunc(handleAWAY))
c.Handlers.register(true, CAP_ACCOUNT, HandlerFunc(handleACCOUNT))
c.Handlers.register(true, ALLEVENTS, HandlerFunc(handleTags))
c.Handlers.register(true, ALL_EVENTS, HandlerFunc(handleTags))
// SASL IRCv3 support.
c.Handlers.register(true, AUTHENTICATE, HandlerFunc(handleSASL))
@ -87,13 +87,14 @@ func handleConnect(c *Client, e Event) {
// the one we supplied during connection, but some networks will rename
// users on connect.
if len(e.Params) > 0 {
c.state.mu.Lock()
c.state.Lock()
c.state.nick = e.Params[0]
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_GENERAL)
}
time.Sleep(2 * time.Second)
c.RunHandlers(&Event{Command: CONNECTED, Trailing: c.Server()})
}
@ -123,24 +124,39 @@ func handleJOIN(c *Client, e Event) {
return
}
var channel string
var channelName string
if len(e.Params) > 0 {
channel = e.Params[0]
channelName = e.Params[0]
} else {
channel = e.Trailing
channelName = e.Trailing
}
// Create the user in state. This will also verify the channel.
c.state.mu.Lock()
user := c.state.createUserIfNotExists(channel, e.Source.Name)
c.state.mu.Unlock()
if user == nil {
if e.Source.Name == c.GetNick() {
if ok := c.state.createChannel(channelName); !ok {
return
}
}
c.state.Lock()
if ok := c.state.createUser(e.Source.Name); !ok {
c.state.Unlock()
return
}
channel := c.state.lookupChannel(channelName)
user := c.state.lookupUser(e.Source.Name)
if channel == nil || user == nil {
return
}
defer c.state.notify(c, UPDATE_STATE)
channel.addUser(user.Nick)
user.addChannel(channel.Name)
// Assume extended-join (ircv3).
if len(e.Params) == 2 {
c.state.mu.Lock()
if e.Params[1] != "*" {
user.Extras.Account = e.Params[1]
}
@ -148,23 +164,23 @@ func handleJOIN(c *Client, e Event) {
if len(e.Trailing) > 0 {
user.Extras.Name = e.Trailing
}
c.state.mu.Unlock()
}
c.state.Unlock()
if e.Source.Name == c.GetNick() {
// If it's us, don't just add our user to the list. Run a WHO which
// will tell us who exactly is in the entire channel.
c.Send(&Event{Command: WHO, Params: []string{channel, "%tacuhnr,1"}})
c.Send(&Event{Command: WHO, Params: []string{channelName, "%tacuhnr,1"}})
// Also send a MODE to obtain the list of channel modes.
c.Send(&Event{Command: MODE, Params: []string{channel}})
c.Send(&Event{Command: MODE, Params: []string{channelName}})
// Update our ident and host too, in state -- since there is no
// cleaner method to do this.
c.state.mu.Lock()
c.state.Lock()
c.state.ident = e.Source.Ident
c.state.host = e.Source.Host
c.state.mu.Unlock()
c.state.Unlock()
return
}
@ -189,16 +205,18 @@ func handlePART(c *Client, e Event) {
return
}
defer c.state.notify(c, UPDATE_STATE)
if e.Source.Name == c.GetNick() {
c.state.mu.Lock()
c.state.Lock()
c.state.deleteChannel(channel)
c.state.mu.Unlock()
c.state.Unlock()
return
}
c.state.mu.Lock()
c.state.Lock()
c.state.deleteUser(channel, e.Source.Name)
c.state.mu.Unlock()
c.state.Unlock()
}
// handleTOPIC handles incoming TOPIC events and keeps channel tracking info
@ -214,21 +232,22 @@ func handleTOPIC(c *Client, e Event) {
name = e.Params[len(e.Params)-1]
}
c.state.mu.Lock()
channel := c.state.createChanIfNotExists(name)
c.state.Lock()
channel := c.state.lookupChannel(name)
if channel == nil {
c.state.mu.Unlock()
c.state.Unlock()
return
}
channel.Topic = e.Trailing
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_STATE)
}
// handlWHO updates our internal tracking of users/channels with WHO/WHOX
// information.
func handleWHO(c *Client, e Event) {
var channel, ident, host, nick, account, realname string
var ident, host, nick, account, realname string
// Assume WHOX related.
if e.Command == RPL_WHOSPCRPL {
@ -244,20 +263,20 @@ func handleWHO(c *Client, e Event) {
return
}
channel, ident, host, nick, account = e.Params[2], e.Params[3], e.Params[4], e.Params[5], e.Params[6]
ident, host, nick, account = e.Params[3], e.Params[4], e.Params[5], e.Params[6]
realname = e.Trailing
} else {
// Assume RPL_WHOREPLY.
channel, ident, host, nick = e.Params[1], e.Params[2], e.Params[3], e.Params[5]
ident, host, nick = e.Params[2], e.Params[3], e.Params[5]
if len(e.Trailing) > 2 {
realname = e.Trailing[2:]
}
}
c.state.mu.Lock()
user := c.state.createUserIfNotExists(channel, nick)
c.state.Lock()
user := c.state.lookupUser(nick)
if user == nil {
c.state.mu.Unlock()
c.state.Unlock()
return
}
@ -269,7 +288,8 @@ func handleWHO(c *Client, e Event) {
user.Extras.Account = account
}
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_STATE)
}
// handleKICK ensures that users are cleaned up after being kicked from the
@ -280,17 +300,19 @@ func handleKICK(c *Client, e Event) {
return
}
defer c.state.notify(c, UPDATE_STATE)
if e.Params[1] == c.GetNick() {
c.state.mu.Lock()
c.state.Lock()
c.state.deleteChannel(e.Params[0])
c.state.mu.Unlock()
c.state.Unlock()
return
}
// Assume it's just another user.
c.state.mu.Lock()
c.state.Lock()
c.state.deleteUser(e.Params[0], e.Params[1])
c.state.mu.Unlock()
c.state.Unlock()
}
// handleNICK ensures that users are renamed in state, or the client name is
@ -300,14 +322,15 @@ func handleNICK(c *Client, e Event) {
return
}
c.state.mu.Lock()
c.state.Lock()
// renameUser updates the LastActive time automatically.
if len(e.Params) == 1 {
c.state.renameUser(e.Source.Name, e.Params[0])
} else if len(e.Trailing) > 0 {
c.state.renameUser(e.Source.Name, e.Trailing)
}
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_STATE)
}
// handleQUIT handles users that are quitting from the network.
@ -320,9 +343,10 @@ func handleQUIT(c *Client, e Event) {
return
}
c.state.mu.Lock()
c.state.Lock()
c.state.deleteUser("", e.Source.Name)
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_STATE)
}
// handleMYINFO handles incoming MYINFO events -- these are commonly used
@ -335,10 +359,11 @@ func handleMYINFO(c *Client, e Event) {
return
}
c.state.mu.Lock()
c.state.Lock()
c.state.serverOptions["SERVER"] = e.Params[1]
c.state.serverOptions["VERSION"] = e.Params[2]
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_GENERAL)
}
// handleISUPPORT handles incoming RPL_ISUPPORT (also known as RPL_PROTOCTL)
@ -359,7 +384,7 @@ func handleISUPPORT(c *Client, e Event) {
return
}
c.state.mu.Lock()
c.state.Lock()
// Skip the first parameter, as it's our nickname.
for i := 1; i < len(e.Params); i++ {
j := strings.IndexByte(e.Params[i], 0x3D) // =
@ -373,19 +398,22 @@ func handleISUPPORT(c *Client, e Event) {
val := e.Params[i][j+1:]
c.state.serverOptions[name] = val
}
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_GENERAL)
}
// handleMOTD handles incoming MOTD messages and buffers them up for use with
// Client.ServerMOTD().
func handleMOTD(c *Client, e Event) {
c.state.mu.Lock()
c.state.Lock()
defer c.state.notify(c, UPDATE_GENERAL)
// Beginning of the MOTD.
if e.Command == RPL_MOTDSTART {
c.state.motd = ""
c.state.mu.Unlock()
c.state.Unlock()
return
}
@ -395,15 +423,19 @@ func handleMOTD(c *Client, e Event) {
}
c.state.motd += e.Trailing
c.state.mu.Unlock()
c.state.Unlock()
}
// handleNAMES handles incoming NAMES queries, of which lists all users in
// a given channel. Optionally also obtains ident/host values, as well as
// permissions for each user, depending on what capabilities are enabled.
func handleNAMES(c *Client, e Event) {
if len(e.Params) < 1 || !IsValidChannel(e.Params[len(e.Params)-1]) {
if len(e.Params) < 1 {
return
}
channel := c.state.lookupChannel(e.Params[len(e.Params)-1])
if channel == nil {
return
}
@ -412,7 +444,7 @@ func handleNAMES(c *Client, e Event) {
var host, ident, modes, nick string
var ok bool
c.state.mu.Lock()
c.state.Lock()
for i := 0; i < len(parts); i++ {
modes, nick, ok = parseUserPrefix(parts[i])
if !ok {
@ -435,11 +467,15 @@ func handleNAMES(c *Client, e Event) {
continue
}
user := c.state.createUserIfNotExists(e.Params[len(e.Params)-1], nick)
c.state.createUser(nick)
user := c.state.lookupUser(nick)
if user == nil {
continue
}
user.addChannel(channel.Name)
channel.addUser(nick)
// Add necessary userhost-in-names data into the user.
if host != "" {
user.Host = host
@ -451,7 +487,8 @@ func handleNAMES(c *Client, e Event) {
// Don't append modes, overwrite them.
user.Perms.set(modes, false)
}
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_STATE)
}
// updateLastActive is a wrapper for any event which the source author
@ -463,14 +500,15 @@ func updateLastActive(c *Client, e Event) {
return
}
c.state.mu.Lock()
defer c.state.mu.Unlock()
c.state.Lock()
// Update the users last active time, if they exist.
user := c.state.lookupUser(e.Source.Name)
if user == nil {
c.state.Unlock()
return
}
user.LastActive = time.Now()
c.state.Unlock()
}

32
cap.go
View File

@ -92,7 +92,7 @@ func handleCAP(c *Client, e Event) {
possible := possibleCapList(c)
if len(e.Params) >= 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_LS {
c.state.mu.Lock()
c.state.Lock()
caps := parseCap(e.Trailing)
@ -124,7 +124,7 @@ func handleCAP(c *Client, e Event) {
c.state.tmpCap = append(c.state.tmpCap, k)
}
c.state.mu.Unlock()
c.state.Unlock()
// Indicates if this is a multi-line LS. (2 args means it's the
// last LS).
@ -140,14 +140,14 @@ func handleCAP(c *Client, e Event) {
// Re-initialize the tmpCap, so if we get multiple 'CAP LS' requests
// due to cap-notify, we can re-evaluate what we can support.
c.state.mu.Lock()
c.state.Lock()
c.state.tmpCap = []string{}
c.state.mu.Unlock()
c.state.Unlock()
}
}
if len(e.Params) == 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_ACK {
c.state.mu.Lock()
c.state.Lock()
c.state.enabledCap = strings.Split(e.Trailing, " ")
// Do we need to do sasl auth?
@ -158,7 +158,7 @@ func handleCAP(c *Client, e Event) {
break
}
}
c.state.mu.Unlock()
c.state.Unlock()
if wantsSASL {
c.write(&Event{Command: AUTHENTICATE, Params: []string{c.Config.SASL.Method()}})
@ -320,24 +320,26 @@ func handleCHGHOST(c *Client, e Event) {
return
}
c.state.mu.Lock()
c.state.Lock()
user := c.state.lookupUser(e.Source.Name)
if user != nil {
user.Ident = e.Params[0]
user.Host = e.Params[1]
}
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_STATE)
}
// handleAWAY handles incoming IRCv3 AWAY events, for which are sent both
// when users are no longer away, or when they are away.
func handleAWAY(c *Client, e Event) {
c.state.mu.Lock()
c.state.Lock()
user := c.state.lookupUser(e.Source.Name)
if user != nil {
user.Extras.Away = e.Trailing
}
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_STATE)
}
// handleACCOUNT handles incoming IRCv3 ACCOUNT events. ACCOUNT is sent when
@ -354,12 +356,13 @@ func handleACCOUNT(c *Client, e Event) {
account = ""
}
c.state.mu.Lock()
c.state.Lock()
user := c.state.lookupUser(e.Source.Name)
if user != nil {
user.Extras.Account = account
}
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_STATE)
}
// handleTags handles any messages that have tags that will affect state. (e.g.
@ -374,12 +377,13 @@ func handleTags(c *Client, e Event) {
return
}
c.state.mu.Lock()
c.state.Lock()
user := c.state.lookupUser(e.Source.Name)
if user != nil {
user.Extras.Account = account
}
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_STATE)
}
const (

View File

@ -322,9 +322,10 @@ func (c *Client) DisableTracking() {
c.Config.disableTracking = true
c.Handlers.clearInternal()
c.state.mu.Lock()
c.state.Lock()
c.state.channels = nil
c.state.mu.Unlock()
c.state.Unlock()
c.state.notify(c, UPDATE_STATE)
c.registerBuiltins()
}
@ -388,8 +389,8 @@ func (c *Client) IsConnected() (connected bool) {
func (c *Client) GetNick() string {
c.panicIfNotTracking()
c.state.mu.RLock()
defer c.state.mu.RUnlock()
c.state.RLock()
defer c.state.RUnlock()
if c.state.nick == "" {
return c.Config.Nick
@ -404,8 +405,8 @@ func (c *Client) GetNick() string {
func (c *Client) GetIdent() string {
c.panicIfNotTracking()
c.state.mu.RLock()
defer c.state.mu.RUnlock()
c.state.RLock()
defer c.state.RUnlock()
if c.state.ident == "" {
return c.Config.User
@ -420,8 +421,8 @@ func (c *Client) GetIdent() string {
func (c *Client) GetHost() string {
c.panicIfNotTracking()
c.state.mu.RLock()
defer c.state.mu.RUnlock()
c.state.RLock()
defer c.state.RUnlock()
return c.state.host
}
@ -431,14 +432,14 @@ func (c *Client) GetHost() string {
func (c *Client) Channels() []string {
c.panicIfNotTracking()
c.state.mu.RLock()
c.state.RLock()
channels := make([]string, len(c.state.channels))
var i int
for channel := range c.state.channels {
channels[i] = channel
i++
}
c.state.mu.RUnlock()
c.state.RUnlock()
sort.Strings(channels)
return channels
@ -449,14 +450,14 @@ func (c *Client) Channels() []string {
func (c *Client) Users() []string {
c.panicIfNotTracking()
c.state.mu.RLock()
c.state.RLock()
users := make([]string, len(c.state.users))
var i int
for user := range c.state.users {
users[i] = user
i++
}
c.state.mu.RUnlock()
c.state.RUnlock()
sort.Strings(users)
return users
@ -470,10 +471,10 @@ func (c *Client) LookupChannel(name string) *Channel {
return nil
}
c.state.mu.Lock()
c.state.RLock()
defer c.state.RUnlock()
channel := c.state.lookupChannel(name)
c.state.mu.Unlock()
if channel == nil {
return nil
}
@ -489,10 +490,10 @@ func (c *Client) LookupUser(nick string) *User {
return nil
}
c.state.mu.Lock()
c.state.RLock()
defer c.state.RUnlock()
user := c.state.lookupUser(nick)
c.state.mu.Unlock()
if user == nil {
return nil
}
@ -505,9 +506,9 @@ func (c *Client) LookupUser(nick string) *User {
func (c *Client) IsInChannel(channel string) bool {
c.panicIfNotTracking()
c.state.mu.RLock()
c.state.RLock()
_, inChannel := c.state.channels[ToRFC1459(channel)]
c.state.mu.RUnlock()
c.state.RUnlock()
return inChannel
}
@ -521,9 +522,9 @@ func (c *Client) IsInChannel(channel string) bool {
func (c *Client) GetServerOption(key string) (result string, ok bool) {
c.panicIfNotTracking()
c.state.mu.Lock()
c.state.RLock()
result, ok = c.state.serverOptions[key]
c.state.mu.Unlock()
c.state.RUnlock()
return result, ok
}
@ -567,9 +568,9 @@ func (c *Client) ServerVersion() (version string) {
func (c *Client) ServerMOTD() (motd string) {
c.panicIfNotTracking()
c.state.mu.Lock()
c.state.RLock()
motd = c.state.motd
c.state.mu.Unlock()
c.state.RUnlock()
return motd
}

View File

@ -27,8 +27,8 @@ func TestDisableTracking(t *testing.T) {
t.Fatal("Client.Handlers contains capability tracking handlers, though disabled")
}
client.state.mu.Lock()
defer client.state.mu.Unlock()
client.state.Lock()
defer client.state.Unlock()
if client.state.channels != nil {
t.Fatal("Client.DisableTracking() called but channel state still exists")

View File

@ -444,7 +444,7 @@ func (c *Client) sendLoop(errs chan error, done chan struct{}, wg *sync.WaitGrou
// Check if tags exist on the event. If they do, and message-tags
// isn't a supported capability, remove them from the event.
if event.Tags != nil {
c.state.mu.Lock()
c.state.RLock()
var in bool
for i := 0; i < len(c.state.enabledCap); i++ {
if c.state.enabledCap[i] == "message-tags" {
@ -452,7 +452,7 @@ func (c *Client) sendLoop(errs chan error, done chan struct{}, wg *sync.WaitGrou
break
}
}
c.state.mu.Unlock()
c.state.RUnlock()
if !in {
event.Tags = Tags{}

View File

@ -20,11 +20,13 @@ const (
// Emulated event commands used to allow easier hooks into the changing
// state of the client.
const (
ALLEVENTS = "*" // trigger on all events
CONNECTED = "CONNECTED" // when it's safe to send arbitrary commands (joins, list, who, etc), trailing is host:port
INITIALIZED = "INIT" // verifies successful socket connection, trailing is host:port
DISCONNECTED = "DISCONNECTED" // occurs when we're disconnected from the server (user-requested or not)
STOPPED = "STOPPED" // occurs when Client.Stop() has been called
UPDATE_STATE = "CLIENT_STATE_UPDATED" // when channel/user state is updated.
UPDATE_GENERAL = "CLIENT_GENERAL_UPDATED" // when general state (client nick, server name, etc) is updated.
ALL_EVENTS = "*" // trigger on all events
CONNECTED = "CLIENT_CONNECTED" // when it's safe to send arbitrary commands (joins, list, who, etc), trailing is host:port
INITIALIZED = "CLIENT_INIT" // verifies successful socket connection, trailing is host:port
DISCONNECTED = "CLIENT_DISCONNECTED" // occurs when we're disconnected from the server (user-requested or not)
STOPPED = "CLIENT_STOPPED" // occurs when Client.Stop() has been called
)
// User/channel prefixes :: RFC1459.

View File

@ -30,7 +30,7 @@ func (c *Client) RunHandlers(event *Event) {
}
// Regular wildcard handlers.
c.Handlers.exec(ALLEVENTS, c, event.Copy())
c.Handlers.exec(ALL_EVENTS, c, event.Copy())
// Then regular handlers.
c.Handlers.exec(event.Command, c, event.Copy())

View File

@ -329,10 +329,10 @@ func handleMODE(c *Client, e Event) {
return
}
c.state.mu.Lock()
c.state.RLock()
channel := c.state.lookupChannel(e.Params[0])
if channel == nil {
c.state.mu.Unlock()
c.state.RUnlock()
return
}
@ -357,7 +357,8 @@ func handleMODE(c *Client, e Event) {
}
}
c.state.mu.Unlock()
c.state.RUnlock()
c.state.notify(c, UPDATE_STATE)
}
// chanModes returns the ISUPPORT list of server-supported channel modes,

108
state.go
View File

@ -11,11 +11,10 @@ import (
)
// state represents the actively-changing variables within the client
// runtime.
// runtime. Note that everything within the state should be guarded by the
// embedded sync.RWMutex.
type state struct {
// m is a RW mutex lock, used to guard the state from goroutines causing
// corruption.
mu sync.RWMutex
sync.RWMutex
// nick, ident, and host are the internal trackers for our user.
nick, ident, host string
// channels represents all channels we're active in.
@ -36,9 +35,15 @@ type state struct {
motd string
}
// notify sends state change notifications so users can update their refs
// when state changes.
func (s *state) notify(c *Client, ntype string) {
c.RunHandlers(&Event{Command: ntype})
}
// reset resets the state back to it's original form.
func (s *state) reset() {
s.mu.Lock()
s.Lock()
s.nick = ""
s.ident = ""
s.host = ""
@ -47,7 +52,7 @@ func (s *state) reset() {
s.serverOptions = make(map[string]string)
s.enabledCap = []string{}
s.motd = ""
s.mu.Unlock()
s.Unlock()
}
// User represents an IRC user and the state attached to them.
@ -113,6 +118,13 @@ func (u *User) Copy() *User {
return nu
}
// addChannel adds the channel to the users channel list.
func (u *User) addChannel(name string) {
u.Channels = append(u.Channels, ToRFC1459(name))
sort.StringsAreSorted(u.Channels)
}
// deleteChannel removes an existing channel from the users channel list.
func (u *User) deleteChannel(name string) {
name = ToRFC1459(name)
@ -175,6 +187,13 @@ type Channel struct {
Modes CModes
}
// addUser adds a user to the users list.
func (c *Channel) addUser(nick string) {
c.Users = append(c.Users, ToRFC1459(nick))
sort.Strings(c.Users)
}
// deleteUser removes an existing user from the users list.
func (c *Channel) deleteUser(nick string) {
nick = ToRFC1459(nick)
@ -228,34 +247,26 @@ func (c *Channel) Lifetime() time.Duration {
return time.Since(c.Joined)
}
// createChanIfNotExists creates the channel in state, if not already done.
// Always use state.mu for transaction.
func (s *state) createChanIfNotExists(name string) (channel *Channel) {
// Not a valid channel.
if !IsValidChannel(name) {
return nil
}
// createChannel creates the channel in state, if not already done.
func (s *state) createChannel(name string) (ok bool) {
supported := s.chanModes()
prefixes, _ := parsePrefixes(s.userPrefixes())
if _, ok := s.channels[ToRFC1459(name)]; ok {
return s.channels[ToRFC1459(name)]
return false
}
channel = &Channel{
s.channels[ToRFC1459(name)] = &Channel{
Name: name,
Users: []string{},
Joined: time.Now(),
Modes: NewCModes(supported, prefixes),
}
s.channels[ToRFC1459(name)] = channel
return channel
return true
}
// deleteChannel removes the channel from state, if not already done. Always
// use state.mu for transaction.
// deleteChannel removes the channel from state, if not already done.
func (s *state) deleteChannel(name string) {
name = ToRFC1459(name)
@ -279,67 +290,35 @@ func (s *state) deleteChannel(name string) {
}
// lookupChannel returns a reference to a channel, nil returned if no results
// found. Always use state.mu for transaction.
// found.
func (s *state) lookupChannel(name string) *Channel {
if !IsValidChannel(name) {
return nil
}
return s.channels[ToRFC1459(name)]
}
// lookupUser returns a reference to a user, nil returned if no results
// found. Always use state.mu for transaction.
// found.
func (s *state) lookupUser(name string) *User {
if !IsValidNick(name) {
return nil
}
return s.users[ToRFC1459(name)]
}
// createUserIfNotExists creates the channel and user in state, if not already
// done. Always use state.mu for transaction.
func (s *state) createUserIfNotExists(channelName, nick string) (user *User) {
if !IsValidNick(nick) {
return nil
// createUser creates the user in state, if not already done.
func (s *state) createUser(nick string) (ok bool) {
if _, ok := s.users[ToRFC1459(nick)]; ok {
// User already exists.
return false
}
channel := s.createChanIfNotExists(channelName)
if channel == nil {
return
}
user = s.lookupUser(nick)
if user != nil {
if !user.InChannel(channelName) {
user.Channels = append(user.Channels, ToRFC1459(channelName))
sort.StringsAreSorted(user.Channels)
}
user.LastActive = time.Now()
return user
}
user = &User{
s.users[ToRFC1459(nick)] = &User{
Nick: nick,
FirstSeen: time.Now(),
LastActive: time.Now(),
}
s.users[ToRFC1459(nick)] = user
channel.Users = append(channel.Users, ToRFC1459(nick))
sort.Strings(channel.Users)
return user
return true
}
// deleteUser removes the user from channel state. Always use state.mu for
// transaction.
// deleteUser removes the user from channel state.
func (s *state) deleteUser(channelName, nick string) {
if !IsValidNick(nick) {
return
}
user := s.lookupUser(nick)
if user == nil {
return
@ -371,12 +350,7 @@ func (s *state) deleteUser(channelName, nick string) {
}
// renameUser renames the user in state, in all locations where relevant.
// Always use state.mu for transaction.
func (s *state) renameUser(from, to string) {
if !IsValidNick(from) || !IsValidNick(to) {
return
}
from = ToRFC1459(from)
// Update our nickname.