diff --git a/builtin.go b/builtin.go index 9b5074e..f66581f 100644 --- a/builtin.go +++ b/builtin.go @@ -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() } diff --git a/cap.go b/cap.go index ff733ec..0ea12e0 100644 --- a/cap.go +++ b/cap.go @@ -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 ( diff --git a/client.go b/client.go index 8077321..c459b50 100644 --- a/client.go +++ b/client.go @@ -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 } diff --git a/client_test.go b/client_test.go index f401ef6..d687bc2 100644 --- a/client_test.go +++ b/client_test.go @@ -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") diff --git a/conn.go b/conn.go index 539db4d..cce6ab5 100644 --- a/conn.go +++ b/conn.go @@ -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{} diff --git a/contants.go b/contants.go index d627413..4d3c65b 100644 --- a/contants.go +++ b/contants.go @@ -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. diff --git a/handler.go b/handler.go index ef12a59..f0c737f 100644 --- a/handler.go +++ b/handler.go @@ -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()) diff --git a/modes.go b/modes.go index de8c9b2..f109fe1 100644 --- a/modes.go +++ b/modes.go @@ -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, diff --git a/state.go b/state.go index 37f864c..50da7a4 100644 --- a/state.go +++ b/state.go @@ -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.