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_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
32
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 (
|
||||
|
|
45
client.go
45
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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
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
|
||||
// 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{}
|
||||
|
|
12
contants.go
12
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.
|
||||
|
|
|
@ -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())
|
||||
|
|
7
modes.go
7
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,
|
||||
|
|
108
state.go
108
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.
|
||||
|
|
Loading…
Reference in New Issue