cleanup state mutexs, add state change events
This commit is contained in:
parent
bd2c3e31ea
commit
9e08ab340d
148
builtin.go
148
builtin.go
@ -58,7 +58,7 @@ func (c *Client) registerBuiltins() {
|
|||||||
c.Handlers.register(true, CAP_CHGHOST, HandlerFunc(handleCHGHOST))
|
c.Handlers.register(true, CAP_CHGHOST, HandlerFunc(handleCHGHOST))
|
||||||
c.Handlers.register(true, CAP_AWAY, HandlerFunc(handleAWAY))
|
c.Handlers.register(true, CAP_AWAY, HandlerFunc(handleAWAY))
|
||||||
c.Handlers.register(true, CAP_ACCOUNT, HandlerFunc(handleACCOUNT))
|
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.
|
// SASL IRCv3 support.
|
||||||
c.Handlers.register(true, AUTHENTICATE, HandlerFunc(handleSASL))
|
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
|
// the one we supplied during connection, but some networks will rename
|
||||||
// users on connect.
|
// users on connect.
|
||||||
if len(e.Params) > 0 {
|
if len(e.Params) > 0 {
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
c.state.nick = e.Params[0]
|
c.state.nick = e.Params[0]
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
|
|
||||||
|
c.state.notify(c, UPDATE_GENERAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
c.RunHandlers(&Event{Command: CONNECTED, Trailing: c.Server()})
|
c.RunHandlers(&Event{Command: CONNECTED, Trailing: c.Server()})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,24 +124,39 @@ func handleJOIN(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var channel string
|
var channelName string
|
||||||
if len(e.Params) > 0 {
|
if len(e.Params) > 0 {
|
||||||
channel = e.Params[0]
|
channelName = e.Params[0]
|
||||||
} else {
|
} else {
|
||||||
channel = e.Trailing
|
channelName = e.Trailing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the user in state. This will also verify the channel.
|
if e.Source.Name == c.GetNick() {
|
||||||
c.state.mu.Lock()
|
if ok := c.state.createChannel(channelName); !ok {
|
||||||
user := c.state.createUserIfNotExists(channel, e.Source.Name)
|
return
|
||||||
c.state.mu.Unlock()
|
}
|
||||||
if user == nil {
|
}
|
||||||
|
|
||||||
|
c.state.Lock()
|
||||||
|
if ok := c.state.createUser(e.Source.Name); !ok {
|
||||||
|
c.state.Unlock()
|
||||||
return
|
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).
|
// Assume extended-join (ircv3).
|
||||||
if len(e.Params) == 2 {
|
if len(e.Params) == 2 {
|
||||||
c.state.mu.Lock()
|
|
||||||
if e.Params[1] != "*" {
|
if e.Params[1] != "*" {
|
||||||
user.Extras.Account = e.Params[1]
|
user.Extras.Account = e.Params[1]
|
||||||
}
|
}
|
||||||
@ -148,23 +164,23 @@ func handleJOIN(c *Client, e Event) {
|
|||||||
if len(e.Trailing) > 0 {
|
if len(e.Trailing) > 0 {
|
||||||
user.Extras.Name = e.Trailing
|
user.Extras.Name = e.Trailing
|
||||||
}
|
}
|
||||||
c.state.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
c.state.Unlock()
|
||||||
|
|
||||||
if e.Source.Name == c.GetNick() {
|
if e.Source.Name == c.GetNick() {
|
||||||
// If it's us, don't just add our user to the list. Run a WHO which
|
// 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.
|
// 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.
|
// 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
|
// Update our ident and host too, in state -- since there is no
|
||||||
// cleaner method to do this.
|
// cleaner method to do this.
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
c.state.ident = e.Source.Ident
|
c.state.ident = e.Source.Ident
|
||||||
c.state.host = e.Source.Host
|
c.state.host = e.Source.Host
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,16 +205,18 @@ func handlePART(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer c.state.notify(c, UPDATE_STATE)
|
||||||
|
|
||||||
if e.Source.Name == c.GetNick() {
|
if e.Source.Name == c.GetNick() {
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
c.state.deleteChannel(channel)
|
c.state.deleteChannel(channel)
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
c.state.deleteUser(channel, e.Source.Name)
|
c.state.deleteUser(channel, e.Source.Name)
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleTOPIC handles incoming TOPIC events and keeps channel tracking info
|
// 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]
|
name = e.Params[len(e.Params)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
channel := c.state.createChanIfNotExists(name)
|
channel := c.state.lookupChannel(name)
|
||||||
if channel == nil {
|
if channel == nil {
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.Topic = e.Trailing
|
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
|
// handlWHO updates our internal tracking of users/channels with WHO/WHOX
|
||||||
// information.
|
// information.
|
||||||
func handleWHO(c *Client, e Event) {
|
func handleWHO(c *Client, e Event) {
|
||||||
var channel, ident, host, nick, account, realname string
|
var ident, host, nick, account, realname string
|
||||||
|
|
||||||
// Assume WHOX related.
|
// Assume WHOX related.
|
||||||
if e.Command == RPL_WHOSPCRPL {
|
if e.Command == RPL_WHOSPCRPL {
|
||||||
@ -244,20 +263,20 @@ func handleWHO(c *Client, e Event) {
|
|||||||
return
|
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
|
realname = e.Trailing
|
||||||
} else {
|
} else {
|
||||||
// Assume RPL_WHOREPLY.
|
// 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 {
|
if len(e.Trailing) > 2 {
|
||||||
realname = e.Trailing[2:]
|
realname = e.Trailing[2:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
user := c.state.createUserIfNotExists(channel, nick)
|
user := c.state.lookupUser(nick)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,7 +288,8 @@ func handleWHO(c *Client, e Event) {
|
|||||||
user.Extras.Account = account
|
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
|
// handleKICK ensures that users are cleaned up after being kicked from the
|
||||||
@ -280,17 +300,19 @@ func handleKICK(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer c.state.notify(c, UPDATE_STATE)
|
||||||
|
|
||||||
if e.Params[1] == c.GetNick() {
|
if e.Params[1] == c.GetNick() {
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
c.state.deleteChannel(e.Params[0])
|
c.state.deleteChannel(e.Params[0])
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assume it's just another user.
|
// Assume it's just another user.
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
c.state.deleteUser(e.Params[0], e.Params[1])
|
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
|
// handleNICK ensures that users are renamed in state, or the client name is
|
||||||
@ -300,14 +322,15 @@ func handleNICK(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
// renameUser updates the LastActive time automatically.
|
// renameUser updates the LastActive time automatically.
|
||||||
if len(e.Params) == 1 {
|
if len(e.Params) == 1 {
|
||||||
c.state.renameUser(e.Source.Name, e.Params[0])
|
c.state.renameUser(e.Source.Name, e.Params[0])
|
||||||
} else if len(e.Trailing) > 0 {
|
} else if len(e.Trailing) > 0 {
|
||||||
c.state.renameUser(e.Source.Name, e.Trailing)
|
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.
|
// handleQUIT handles users that are quitting from the network.
|
||||||
@ -320,9 +343,10 @@ func handleQUIT(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
c.state.deleteUser("", e.Source.Name)
|
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
|
// handleMYINFO handles incoming MYINFO events -- these are commonly used
|
||||||
@ -335,10 +359,11 @@ func handleMYINFO(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
c.state.serverOptions["SERVER"] = e.Params[1]
|
c.state.serverOptions["SERVER"] = e.Params[1]
|
||||||
c.state.serverOptions["VERSION"] = e.Params[2]
|
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)
|
// handleISUPPORT handles incoming RPL_ISUPPORT (also known as RPL_PROTOCTL)
|
||||||
@ -359,7 +384,7 @@ func handleISUPPORT(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
// Skip the first parameter, as it's our nickname.
|
// Skip the first parameter, as it's our nickname.
|
||||||
for i := 1; i < len(e.Params); i++ {
|
for i := 1; i < len(e.Params); i++ {
|
||||||
j := strings.IndexByte(e.Params[i], 0x3D) // =
|
j := strings.IndexByte(e.Params[i], 0x3D) // =
|
||||||
@ -373,19 +398,22 @@ func handleISUPPORT(c *Client, e Event) {
|
|||||||
val := e.Params[i][j+1:]
|
val := e.Params[i][j+1:]
|
||||||
c.state.serverOptions[name] = val
|
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
|
// handleMOTD handles incoming MOTD messages and buffers them up for use with
|
||||||
// Client.ServerMOTD().
|
// Client.ServerMOTD().
|
||||||
func handleMOTD(c *Client, e Event) {
|
func handleMOTD(c *Client, e Event) {
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
|
|
||||||
|
defer c.state.notify(c, UPDATE_GENERAL)
|
||||||
|
|
||||||
// Beginning of the MOTD.
|
// Beginning of the MOTD.
|
||||||
if e.Command == RPL_MOTDSTART {
|
if e.Command == RPL_MOTDSTART {
|
||||||
c.state.motd = ""
|
c.state.motd = ""
|
||||||
|
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,15 +423,19 @@ func handleMOTD(c *Client, e Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.state.motd += e.Trailing
|
c.state.motd += e.Trailing
|
||||||
|
c.state.Unlock()
|
||||||
c.state.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleNAMES handles incoming NAMES queries, of which lists all users in
|
// handleNAMES handles incoming NAMES queries, of which lists all users in
|
||||||
// a given channel. Optionally also obtains ident/host values, as well as
|
// a given channel. Optionally also obtains ident/host values, as well as
|
||||||
// permissions for each user, depending on what capabilities are enabled.
|
// permissions for each user, depending on what capabilities are enabled.
|
||||||
func handleNAMES(c *Client, e Event) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,7 +444,7 @@ func handleNAMES(c *Client, e Event) {
|
|||||||
var host, ident, modes, nick string
|
var host, ident, modes, nick string
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
for i := 0; i < len(parts); i++ {
|
for i := 0; i < len(parts); i++ {
|
||||||
modes, nick, ok = parseUserPrefix(parts[i])
|
modes, nick, ok = parseUserPrefix(parts[i])
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -435,11 +467,15 @@ func handleNAMES(c *Client, e Event) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
user := c.state.createUserIfNotExists(e.Params[len(e.Params)-1], nick)
|
c.state.createUser(nick)
|
||||||
|
user := c.state.lookupUser(nick)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user.addChannel(channel.Name)
|
||||||
|
channel.addUser(nick)
|
||||||
|
|
||||||
// Add necessary userhost-in-names data into the user.
|
// Add necessary userhost-in-names data into the user.
|
||||||
if host != "" {
|
if host != "" {
|
||||||
user.Host = host
|
user.Host = host
|
||||||
@ -451,7 +487,8 @@ func handleNAMES(c *Client, e Event) {
|
|||||||
// Don't append modes, overwrite them.
|
// Don't append modes, overwrite them.
|
||||||
user.Perms.set(modes, false)
|
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
|
// updateLastActive is a wrapper for any event which the source author
|
||||||
@ -463,14 +500,15 @@ func updateLastActive(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
defer c.state.mu.Unlock()
|
|
||||||
|
|
||||||
// Update the users last active time, if they exist.
|
// Update the users last active time, if they exist.
|
||||||
user := c.state.lookupUser(e.Source.Name)
|
user := c.state.lookupUser(e.Source.Name)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
|
c.state.Unlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user.LastActive = time.Now()
|
user.LastActive = time.Now()
|
||||||
|
c.state.Unlock()
|
||||||
}
|
}
|
||||||
|
32
cap.go
32
cap.go
@ -92,7 +92,7 @@ func handleCAP(c *Client, e Event) {
|
|||||||
possible := possibleCapList(c)
|
possible := possibleCapList(c)
|
||||||
|
|
||||||
if len(e.Params) >= 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_LS {
|
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)
|
caps := parseCap(e.Trailing)
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ func handleCAP(c *Client, e Event) {
|
|||||||
|
|
||||||
c.state.tmpCap = append(c.state.tmpCap, k)
|
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
|
// Indicates if this is a multi-line LS. (2 args means it's the
|
||||||
// last LS).
|
// last LS).
|
||||||
@ -140,14 +140,14 @@ func handleCAP(c *Client, e Event) {
|
|||||||
|
|
||||||
// Re-initialize the tmpCap, so if we get multiple 'CAP LS' requests
|
// Re-initialize the tmpCap, so if we get multiple 'CAP LS' requests
|
||||||
// due to cap-notify, we can re-evaluate what we can support.
|
// 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.tmpCap = []string{}
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(e.Params) == 2 && len(e.Trailing) > 1 && e.Params[1] == CAP_ACK {
|
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, " ")
|
c.state.enabledCap = strings.Split(e.Trailing, " ")
|
||||||
|
|
||||||
// Do we need to do sasl auth?
|
// Do we need to do sasl auth?
|
||||||
@ -158,7 +158,7 @@ func handleCAP(c *Client, e Event) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
|
|
||||||
if wantsSASL {
|
if wantsSASL {
|
||||||
c.write(&Event{Command: AUTHENTICATE, Params: []string{c.Config.SASL.Method()}})
|
c.write(&Event{Command: AUTHENTICATE, Params: []string{c.Config.SASL.Method()}})
|
||||||
@ -320,24 +320,26 @@ func handleCHGHOST(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
user := c.state.lookupUser(e.Source.Name)
|
user := c.state.lookupUser(e.Source.Name)
|
||||||
if user != nil {
|
if user != nil {
|
||||||
user.Ident = e.Params[0]
|
user.Ident = e.Params[0]
|
||||||
user.Host = e.Params[1]
|
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
|
// handleAWAY handles incoming IRCv3 AWAY events, for which are sent both
|
||||||
// when users are no longer away, or when they are away.
|
// when users are no longer away, or when they are away.
|
||||||
func handleAWAY(c *Client, e Event) {
|
func handleAWAY(c *Client, e Event) {
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
user := c.state.lookupUser(e.Source.Name)
|
user := c.state.lookupUser(e.Source.Name)
|
||||||
if user != nil {
|
if user != nil {
|
||||||
user.Extras.Away = e.Trailing
|
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
|
// handleACCOUNT handles incoming IRCv3 ACCOUNT events. ACCOUNT is sent when
|
||||||
@ -354,12 +356,13 @@ func handleACCOUNT(c *Client, e Event) {
|
|||||||
account = ""
|
account = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
user := c.state.lookupUser(e.Source.Name)
|
user := c.state.lookupUser(e.Source.Name)
|
||||||
if user != nil {
|
if user != nil {
|
||||||
user.Extras.Account = account
|
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.
|
// handleTags handles any messages that have tags that will affect state. (e.g.
|
||||||
@ -374,12 +377,13 @@ func handleTags(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
user := c.state.lookupUser(e.Source.Name)
|
user := c.state.lookupUser(e.Source.Name)
|
||||||
if user != nil {
|
if user != nil {
|
||||||
user.Extras.Account = account
|
user.Extras.Account = account
|
||||||
}
|
}
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
|
c.state.notify(c, UPDATE_STATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
45
client.go
45
client.go
@ -322,9 +322,10 @@ func (c *Client) DisableTracking() {
|
|||||||
c.Config.disableTracking = true
|
c.Config.disableTracking = true
|
||||||
c.Handlers.clearInternal()
|
c.Handlers.clearInternal()
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.Lock()
|
||||||
c.state.channels = nil
|
c.state.channels = nil
|
||||||
c.state.mu.Unlock()
|
c.state.Unlock()
|
||||||
|
c.state.notify(c, UPDATE_STATE)
|
||||||
|
|
||||||
c.registerBuiltins()
|
c.registerBuiltins()
|
||||||
}
|
}
|
||||||
@ -388,8 +389,8 @@ func (c *Client) IsConnected() (connected bool) {
|
|||||||
func (c *Client) GetNick() string {
|
func (c *Client) GetNick() string {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
|
|
||||||
c.state.mu.RLock()
|
c.state.RLock()
|
||||||
defer c.state.mu.RUnlock()
|
defer c.state.RUnlock()
|
||||||
|
|
||||||
if c.state.nick == "" {
|
if c.state.nick == "" {
|
||||||
return c.Config.Nick
|
return c.Config.Nick
|
||||||
@ -404,8 +405,8 @@ func (c *Client) GetNick() string {
|
|||||||
func (c *Client) GetIdent() string {
|
func (c *Client) GetIdent() string {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
|
|
||||||
c.state.mu.RLock()
|
c.state.RLock()
|
||||||
defer c.state.mu.RUnlock()
|
defer c.state.RUnlock()
|
||||||
|
|
||||||
if c.state.ident == "" {
|
if c.state.ident == "" {
|
||||||
return c.Config.User
|
return c.Config.User
|
||||||
@ -420,8 +421,8 @@ func (c *Client) GetIdent() string {
|
|||||||
func (c *Client) GetHost() string {
|
func (c *Client) GetHost() string {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
|
|
||||||
c.state.mu.RLock()
|
c.state.RLock()
|
||||||
defer c.state.mu.RUnlock()
|
defer c.state.RUnlock()
|
||||||
|
|
||||||
return c.state.host
|
return c.state.host
|
||||||
}
|
}
|
||||||
@ -431,14 +432,14 @@ func (c *Client) GetHost() string {
|
|||||||
func (c *Client) Channels() []string {
|
func (c *Client) Channels() []string {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
|
|
||||||
c.state.mu.RLock()
|
c.state.RLock()
|
||||||
channels := make([]string, len(c.state.channels))
|
channels := make([]string, len(c.state.channels))
|
||||||
var i int
|
var i int
|
||||||
for channel := range c.state.channels {
|
for channel := range c.state.channels {
|
||||||
channels[i] = channel
|
channels[i] = channel
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
c.state.mu.RUnlock()
|
c.state.RUnlock()
|
||||||
sort.Strings(channels)
|
sort.Strings(channels)
|
||||||
|
|
||||||
return channels
|
return channels
|
||||||
@ -449,14 +450,14 @@ func (c *Client) Channels() []string {
|
|||||||
func (c *Client) Users() []string {
|
func (c *Client) Users() []string {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
|
|
||||||
c.state.mu.RLock()
|
c.state.RLock()
|
||||||
users := make([]string, len(c.state.users))
|
users := make([]string, len(c.state.users))
|
||||||
var i int
|
var i int
|
||||||
for user := range c.state.users {
|
for user := range c.state.users {
|
||||||
users[i] = user
|
users[i] = user
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
c.state.mu.RUnlock()
|
c.state.RUnlock()
|
||||||
sort.Strings(users)
|
sort.Strings(users)
|
||||||
|
|
||||||
return users
|
return users
|
||||||
@ -470,10 +471,10 @@ func (c *Client) LookupChannel(name string) *Channel {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.RLock()
|
||||||
|
defer c.state.RUnlock()
|
||||||
|
|
||||||
channel := c.state.lookupChannel(name)
|
channel := c.state.lookupChannel(name)
|
||||||
c.state.mu.Unlock()
|
|
||||||
if channel == nil {
|
if channel == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -489,10 +490,10 @@ func (c *Client) LookupUser(nick string) *User {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.RLock()
|
||||||
|
defer c.state.RUnlock()
|
||||||
|
|
||||||
user := c.state.lookupUser(nick)
|
user := c.state.lookupUser(nick)
|
||||||
c.state.mu.Unlock()
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -505,9 +506,9 @@ func (c *Client) LookupUser(nick string) *User {
|
|||||||
func (c *Client) IsInChannel(channel string) bool {
|
func (c *Client) IsInChannel(channel string) bool {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
|
|
||||||
c.state.mu.RLock()
|
c.state.RLock()
|
||||||
_, inChannel := c.state.channels[ToRFC1459(channel)]
|
_, inChannel := c.state.channels[ToRFC1459(channel)]
|
||||||
c.state.mu.RUnlock()
|
c.state.RUnlock()
|
||||||
|
|
||||||
return inChannel
|
return inChannel
|
||||||
}
|
}
|
||||||
@ -521,9 +522,9 @@ func (c *Client) IsInChannel(channel string) bool {
|
|||||||
func (c *Client) GetServerOption(key string) (result string, ok bool) {
|
func (c *Client) GetServerOption(key string) (result string, ok bool) {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.RLock()
|
||||||
result, ok = c.state.serverOptions[key]
|
result, ok = c.state.serverOptions[key]
|
||||||
c.state.mu.Unlock()
|
c.state.RUnlock()
|
||||||
|
|
||||||
return result, ok
|
return result, ok
|
||||||
}
|
}
|
||||||
@ -567,9 +568,9 @@ func (c *Client) ServerVersion() (version string) {
|
|||||||
func (c *Client) ServerMOTD() (motd string) {
|
func (c *Client) ServerMOTD() (motd string) {
|
||||||
c.panicIfNotTracking()
|
c.panicIfNotTracking()
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.RLock()
|
||||||
motd = c.state.motd
|
motd = c.state.motd
|
||||||
c.state.mu.Unlock()
|
c.state.RUnlock()
|
||||||
|
|
||||||
return motd
|
return motd
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,8 @@ func TestDisableTracking(t *testing.T) {
|
|||||||
t.Fatal("Client.Handlers contains capability tracking handlers, though disabled")
|
t.Fatal("Client.Handlers contains capability tracking handlers, though disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
client.state.mu.Lock()
|
client.state.Lock()
|
||||||
defer client.state.mu.Unlock()
|
defer client.state.Unlock()
|
||||||
|
|
||||||
if client.state.channels != nil {
|
if client.state.channels != nil {
|
||||||
t.Fatal("Client.DisableTracking() called but channel state still exists")
|
t.Fatal("Client.DisableTracking() called but channel state still exists")
|
||||||
|
4
conn.go
4
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
|
// Check if tags exist on the event. If they do, and message-tags
|
||||||
// isn't a supported capability, remove them from the event.
|
// isn't a supported capability, remove them from the event.
|
||||||
if event.Tags != nil {
|
if event.Tags != nil {
|
||||||
c.state.mu.Lock()
|
c.state.RLock()
|
||||||
var in bool
|
var in bool
|
||||||
for i := 0; i < len(c.state.enabledCap); i++ {
|
for i := 0; i < len(c.state.enabledCap); i++ {
|
||||||
if c.state.enabledCap[i] == "message-tags" {
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.state.mu.Unlock()
|
c.state.RUnlock()
|
||||||
|
|
||||||
if !in {
|
if !in {
|
||||||
event.Tags = Tags{}
|
event.Tags = Tags{}
|
||||||
|
12
contants.go
12
contants.go
@ -20,11 +20,13 @@ const (
|
|||||||
// Emulated event commands used to allow easier hooks into the changing
|
// Emulated event commands used to allow easier hooks into the changing
|
||||||
// state of the client.
|
// state of the client.
|
||||||
const (
|
const (
|
||||||
ALLEVENTS = "*" // trigger on all events
|
UPDATE_STATE = "CLIENT_STATE_UPDATED" // when channel/user state is updated.
|
||||||
CONNECTED = "CONNECTED" // when it's safe to send arbitrary commands (joins, list, who, etc), trailing is host:port
|
UPDATE_GENERAL = "CLIENT_GENERAL_UPDATED" // when general state (client nick, server name, etc) is updated.
|
||||||
INITIALIZED = "INIT" // verifies successful socket connection, trailing is host:port
|
ALL_EVENTS = "*" // trigger on all events
|
||||||
DISCONNECTED = "DISCONNECTED" // occurs when we're disconnected from the server (user-requested or not)
|
CONNECTED = "CLIENT_CONNECTED" // when it's safe to send arbitrary commands (joins, list, who, etc), trailing is host:port
|
||||||
STOPPED = "STOPPED" // occurs when Client.Stop() has been called
|
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.
|
// User/channel prefixes :: RFC1459.
|
||||||
|
@ -30,7 +30,7 @@ func (c *Client) RunHandlers(event *Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Regular wildcard handlers.
|
// Regular wildcard handlers.
|
||||||
c.Handlers.exec(ALLEVENTS, c, event.Copy())
|
c.Handlers.exec(ALL_EVENTS, c, event.Copy())
|
||||||
|
|
||||||
// Then regular handlers.
|
// Then regular handlers.
|
||||||
c.Handlers.exec(event.Command, c, event.Copy())
|
c.Handlers.exec(event.Command, c, event.Copy())
|
||||||
|
7
modes.go
7
modes.go
@ -329,10 +329,10 @@ func handleMODE(c *Client, e Event) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.state.mu.Lock()
|
c.state.RLock()
|
||||||
channel := c.state.lookupChannel(e.Params[0])
|
channel := c.state.lookupChannel(e.Params[0])
|
||||||
if channel == nil {
|
if channel == nil {
|
||||||
c.state.mu.Unlock()
|
c.state.RUnlock()
|
||||||
return
|
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,
|
// chanModes returns the ISUPPORT list of server-supported channel modes,
|
||||||
|
108
state.go
108
state.go
@ -11,11 +11,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// state represents the actively-changing variables within the client
|
// 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 {
|
type state struct {
|
||||||
// m is a RW mutex lock, used to guard the state from goroutines causing
|
sync.RWMutex
|
||||||
// corruption.
|
|
||||||
mu sync.RWMutex
|
|
||||||
// nick, ident, and host are the internal trackers for our user.
|
// nick, ident, and host are the internal trackers for our user.
|
||||||
nick, ident, host string
|
nick, ident, host string
|
||||||
// channels represents all channels we're active in.
|
// channels represents all channels we're active in.
|
||||||
@ -36,9 +35,15 @@ type state struct {
|
|||||||
motd string
|
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.
|
// reset resets the state back to it's original form.
|
||||||
func (s *state) reset() {
|
func (s *state) reset() {
|
||||||
s.mu.Lock()
|
s.Lock()
|
||||||
s.nick = ""
|
s.nick = ""
|
||||||
s.ident = ""
|
s.ident = ""
|
||||||
s.host = ""
|
s.host = ""
|
||||||
@ -47,7 +52,7 @@ func (s *state) reset() {
|
|||||||
s.serverOptions = make(map[string]string)
|
s.serverOptions = make(map[string]string)
|
||||||
s.enabledCap = []string{}
|
s.enabledCap = []string{}
|
||||||
s.motd = ""
|
s.motd = ""
|
||||||
s.mu.Unlock()
|
s.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// User represents an IRC user and the state attached to them.
|
// User represents an IRC user and the state attached to them.
|
||||||
@ -113,6 +118,13 @@ func (u *User) Copy() *User {
|
|||||||
return nu
|
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) {
|
func (u *User) deleteChannel(name string) {
|
||||||
name = ToRFC1459(name)
|
name = ToRFC1459(name)
|
||||||
|
|
||||||
@ -175,6 +187,13 @@ type Channel struct {
|
|||||||
Modes CModes
|
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) {
|
func (c *Channel) deleteUser(nick string) {
|
||||||
nick = ToRFC1459(nick)
|
nick = ToRFC1459(nick)
|
||||||
|
|
||||||
@ -228,34 +247,26 @@ func (c *Channel) Lifetime() time.Duration {
|
|||||||
return time.Since(c.Joined)
|
return time.Since(c.Joined)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createChanIfNotExists creates the channel in state, if not already done.
|
// createChannel creates the channel in state, if not already done.
|
||||||
// Always use state.mu for transaction.
|
func (s *state) createChannel(name string) (ok bool) {
|
||||||
func (s *state) createChanIfNotExists(name string) (channel *Channel) {
|
|
||||||
// Not a valid channel.
|
|
||||||
if !IsValidChannel(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
supported := s.chanModes()
|
supported := s.chanModes()
|
||||||
prefixes, _ := parsePrefixes(s.userPrefixes())
|
prefixes, _ := parsePrefixes(s.userPrefixes())
|
||||||
|
|
||||||
if _, ok := s.channels[ToRFC1459(name)]; ok {
|
if _, ok := s.channels[ToRFC1459(name)]; ok {
|
||||||
return s.channels[ToRFC1459(name)]
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
channel = &Channel{
|
s.channels[ToRFC1459(name)] = &Channel{
|
||||||
Name: name,
|
Name: name,
|
||||||
Users: []string{},
|
Users: []string{},
|
||||||
Joined: time.Now(),
|
Joined: time.Now(),
|
||||||
Modes: NewCModes(supported, prefixes),
|
Modes: NewCModes(supported, prefixes),
|
||||||
}
|
}
|
||||||
s.channels[ToRFC1459(name)] = channel
|
|
||||||
|
|
||||||
return channel
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteChannel removes the channel from state, if not already done. Always
|
// deleteChannel removes the channel from state, if not already done.
|
||||||
// use state.mu for transaction.
|
|
||||||
func (s *state) deleteChannel(name string) {
|
func (s *state) deleteChannel(name string) {
|
||||||
name = ToRFC1459(name)
|
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
|
// 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 {
|
func (s *state) lookupChannel(name string) *Channel {
|
||||||
if !IsValidChannel(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.channels[ToRFC1459(name)]
|
return s.channels[ToRFC1459(name)]
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookupUser returns a reference to a user, nil returned if no results
|
// 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 {
|
func (s *state) lookupUser(name string) *User {
|
||||||
if !IsValidNick(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.users[ToRFC1459(name)]
|
return s.users[ToRFC1459(name)]
|
||||||
}
|
}
|
||||||
|
|
||||||
// createUserIfNotExists creates the channel and user in state, if not already
|
// createUser creates the user in state, if not already done.
|
||||||
// done. Always use state.mu for transaction.
|
func (s *state) createUser(nick string) (ok bool) {
|
||||||
func (s *state) createUserIfNotExists(channelName, nick string) (user *User) {
|
if _, ok := s.users[ToRFC1459(nick)]; ok {
|
||||||
if !IsValidNick(nick) {
|
// User already exists.
|
||||||
return nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
channel := s.createChanIfNotExists(channelName)
|
s.users[ToRFC1459(nick)] = &User{
|
||||||
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{
|
|
||||||
Nick: nick,
|
Nick: nick,
|
||||||
FirstSeen: time.Now(),
|
FirstSeen: time.Now(),
|
||||||
LastActive: 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
|
// deleteUser removes the user from channel state.
|
||||||
// transaction.
|
|
||||||
func (s *state) deleteUser(channelName, nick string) {
|
func (s *state) deleteUser(channelName, nick string) {
|
||||||
if !IsValidNick(nick) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user := s.lookupUser(nick)
|
user := s.lookupUser(nick)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return
|
return
|
||||||
@ -371,12 +350,7 @@ func (s *state) deleteUser(channelName, nick string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// renameUser renames the user in state, in all locations where relevant.
|
// renameUser renames the user in state, in all locations where relevant.
|
||||||
// Always use state.mu for transaction.
|
|
||||||
func (s *state) renameUser(from, to string) {
|
func (s *state) renameUser(from, to string) {
|
||||||
if !IsValidNick(from) || !IsValidNick(to) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
from = ToRFC1459(from)
|
from = ToRFC1459(from)
|
||||||
|
|
||||||
// Update our nickname.
|
// Update our nickname.
|
||||||
|
Loading…
Reference in New Issue
Block a user