|
@@ -18,9 +18,8 @@ import (
|
18
|
18
|
)
|
19
|
19
|
|
20
|
20
|
var (
|
21
|
|
- ErrNickMissing = errors.New("nick missing")
|
22
|
|
- ErrNicknameInUse = errors.New("nickname in use")
|
23
|
|
- ErrNicknameMismatch = errors.New("nickname mismatch")
|
|
21
|
+ ErrNickMissing = errors.New("nick missing")
|
|
22
|
+ ErrNicknameInUse = errors.New("nickname in use")
|
24
|
23
|
)
|
25
|
24
|
|
26
|
25
|
// ExpandUserHost takes a userhost, and returns an expanded version.
|
|
@@ -37,132 +36,108 @@ func ExpandUserHost(userhost string) (expanded string) {
|
37
|
36
|
return
|
38
|
37
|
}
|
39
|
38
|
|
40
|
|
-// ClientLookupSet represents a way to store, search and lookup clients.
|
41
|
|
-type ClientLookupSet struct {
|
42
|
|
- ByNickMutex sync.RWMutex
|
43
|
|
- ByNick map[string]*Client
|
|
39
|
+// ClientManager keeps track of clients by nick, enforcing uniqueness of casefolded nicks
|
|
40
|
+type ClientManager struct {
|
|
41
|
+ sync.RWMutex // tier 2
|
|
42
|
+ byNick map[string]*Client
|
44
|
43
|
}
|
45
|
44
|
|
46
|
|
-// NewClientLookupSet returns a new lookup set.
|
47
|
|
-func NewClientLookupSet() *ClientLookupSet {
|
48
|
|
- return &ClientLookupSet{
|
49
|
|
- ByNick: make(map[string]*Client),
|
|
45
|
+// NewClientManager returns a new ClientManager.
|
|
46
|
+func NewClientManager() *ClientManager {
|
|
47
|
+ return &ClientManager{
|
|
48
|
+ byNick: make(map[string]*Client),
|
50
|
49
|
}
|
51
|
50
|
}
|
52
|
51
|
|
53
|
|
-// Count returns how many clients are in the lookup set.
|
54
|
|
-func (clients *ClientLookupSet) Count() int {
|
55
|
|
- clients.ByNickMutex.RLock()
|
56
|
|
- defer clients.ByNickMutex.RUnlock()
|
57
|
|
- count := len(clients.ByNick)
|
|
52
|
+// Count returns how many clients are in the manager.
|
|
53
|
+func (clients *ClientManager) Count() int {
|
|
54
|
+ clients.RLock()
|
|
55
|
+ defer clients.RUnlock()
|
|
56
|
+ count := len(clients.byNick)
|
58
|
57
|
return count
|
59
|
58
|
}
|
60
|
59
|
|
61
|
|
-// Has returns whether or not the given client exists.
|
62
|
|
-//TODO(dan): This seems like ripe ground for a race, if code does Has then Get, and assumes the Get will return a client.
|
63
|
|
-func (clients *ClientLookupSet) Has(nick string) bool {
|
|
60
|
+// Get retrieves a client from the manager, if they exist.
|
|
61
|
+func (clients *ClientManager) Get(nick string) *Client {
|
64
|
62
|
casefoldedName, err := CasefoldName(nick)
|
65
|
63
|
if err == nil {
|
66
|
|
- return false
|
67
|
|
- }
|
68
|
|
- clients.ByNickMutex.RLock()
|
69
|
|
- defer clients.ByNickMutex.RUnlock()
|
70
|
|
- _, exists := clients.ByNick[casefoldedName]
|
71
|
|
- return exists
|
72
|
|
-}
|
73
|
|
-
|
74
|
|
-// getNoMutex is used internally, for getting clients when no mutex is required (i.e. is already set).
|
75
|
|
-func (clients *ClientLookupSet) getNoMutex(nick string) *Client {
|
76
|
|
- casefoldedName, err := CasefoldName(nick)
|
77
|
|
- if err == nil {
|
78
|
|
- cli := clients.ByNick[casefoldedName]
|
79
|
|
- return cli
|
80
|
|
- }
|
81
|
|
- return nil
|
82
|
|
-}
|
83
|
|
-
|
84
|
|
-// Get retrieves a client from the set, if they exist.
|
85
|
|
-func (clients *ClientLookupSet) Get(nick string) *Client {
|
86
|
|
- casefoldedName, err := CasefoldName(nick)
|
87
|
|
- if err == nil {
|
88
|
|
- clients.ByNickMutex.RLock()
|
89
|
|
- defer clients.ByNickMutex.RUnlock()
|
90
|
|
- cli := clients.ByNick[casefoldedName]
|
|
64
|
+ clients.RLock()
|
|
65
|
+ defer clients.RUnlock()
|
|
66
|
+ cli := clients.byNick[casefoldedName]
|
91
|
67
|
return cli
|
92
|
68
|
}
|
93
|
69
|
return nil
|
94
|
70
|
}
|
95
|
71
|
|
96
|
|
-// Add adds a client to the lookup set.
|
97
|
|
-func (clients *ClientLookupSet) Add(client *Client, nick string) error {
|
98
|
|
- nick, err := CasefoldName(nick)
|
99
|
|
- if err != nil {
|
100
|
|
- return err
|
101
|
|
- }
|
102
|
|
- clients.ByNickMutex.Lock()
|
103
|
|
- defer clients.ByNickMutex.Unlock()
|
104
|
|
- if clients.getNoMutex(nick) != nil {
|
105
|
|
- return ErrNicknameInUse
|
|
72
|
+func (clients *ClientManager) removeInternal(client *Client) (removed bool) {
|
|
73
|
+ // requires holding ByNickMutex
|
|
74
|
+ oldcfnick := client.NickCasefolded()
|
|
75
|
+ currentEntry, present := clients.byNick[oldcfnick]
|
|
76
|
+ if present {
|
|
77
|
+ if currentEntry == client {
|
|
78
|
+ delete(clients.byNick, oldcfnick)
|
|
79
|
+ removed = true
|
|
80
|
+ } else {
|
|
81
|
+ // this shouldn't happen, but we can ignore it
|
|
82
|
+ client.server.logger.Warning("internal", fmt.Sprintf("clients for nick %s out of sync", oldcfnick))
|
|
83
|
+ }
|
106
|
84
|
}
|
107
|
|
- clients.ByNick[nick] = client
|
108
|
|
- return nil
|
|
85
|
+ return
|
109
|
86
|
}
|
110
|
87
|
|
111
|
88
|
// Remove removes a client from the lookup set.
|
112
|
|
-func (clients *ClientLookupSet) Remove(client *Client) error {
|
|
89
|
+func (clients *ClientManager) Remove(client *Client) error {
|
|
90
|
+ clients.Lock()
|
|
91
|
+ defer clients.Unlock()
|
|
92
|
+
|
113
|
93
|
if !client.HasNick() {
|
114
|
94
|
return ErrNickMissing
|
115
|
95
|
}
|
116
|
|
- clients.ByNickMutex.Lock()
|
117
|
|
- defer clients.ByNickMutex.Unlock()
|
118
|
|
- if clients.getNoMutex(client.nick) != client {
|
119
|
|
- return ErrNicknameMismatch
|
120
|
|
- }
|
121
|
|
- delete(clients.ByNick, client.nickCasefolded)
|
|
96
|
+ clients.removeInternal(client)
|
122
|
97
|
return nil
|
123
|
98
|
}
|
124
|
99
|
|
125
|
|
-// Replace renames an existing client in the lookup set.
|
126
|
|
-func (clients *ClientLookupSet) Replace(oldNick, newNick string, client *Client) error {
|
127
|
|
- // get casefolded nicknames
|
128
|
|
- oldNick, err := CasefoldName(oldNick)
|
129
|
|
- if err != nil {
|
130
|
|
- return err
|
131
|
|
- }
|
132
|
|
- newNick, err = CasefoldName(newNick)
|
|
100
|
+// SetNick sets a client's nickname, validating it against nicknames in use
|
|
101
|
+func (clients *ClientManager) SetNick(client *Client, newNick string) error {
|
|
102
|
+ newcfnick, err := CasefoldName(newNick)
|
133
|
103
|
if err != nil {
|
134
|
104
|
return err
|
135
|
105
|
}
|
136
|
106
|
|
137
|
|
- // remove and replace
|
138
|
|
- clients.ByNickMutex.Lock()
|
139
|
|
- defer clients.ByNickMutex.Unlock()
|
|
107
|
+ clients.Lock()
|
|
108
|
+ defer clients.Unlock()
|
140
|
109
|
|
141
|
|
- oldClient := clients.ByNick[newNick]
|
142
|
|
- if oldClient == nil || oldClient == client {
|
143
|
|
- // whoo
|
144
|
|
- } else {
|
|
110
|
+ clients.removeInternal(client)
|
|
111
|
+ currentNewEntry := clients.byNick[newcfnick]
|
|
112
|
+ // the client may just be changing case
|
|
113
|
+ if currentNewEntry != nil && currentNewEntry != client {
|
145
|
114
|
return ErrNicknameInUse
|
146
|
115
|
}
|
|
116
|
+ clients.byNick[newcfnick] = client
|
|
117
|
+ client.updateNickMask(newNick)
|
|
118
|
+ return nil
|
|
119
|
+}
|
147
|
120
|
|
148
|
|
- if oldNick == newNick {
|
149
|
|
- // if they're only changing case, don't need to remove+re-add them
|
150
|
|
- return nil
|
|
121
|
+func (clients *ClientManager) AllClients() (result []*Client) {
|
|
122
|
+ clients.RLock()
|
|
123
|
+ defer clients.RUnlock()
|
|
124
|
+ result = make([]*Client, len(clients.byNick))
|
|
125
|
+ i := 0
|
|
126
|
+ for _, client := range(clients.byNick) {
|
|
127
|
+ result[i] = client
|
|
128
|
+ i++
|
151
|
129
|
}
|
152
|
|
-
|
153
|
|
- delete(clients.ByNick, oldNick)
|
154
|
|
- clients.ByNick[newNick] = client
|
155
|
|
- return nil
|
|
130
|
+ return
|
156
|
131
|
}
|
157
|
132
|
|
158
|
133
|
// AllWithCaps returns all clients with the given capabilities.
|
159
|
|
-func (clients *ClientLookupSet) AllWithCaps(capabs ...caps.Capability) (set ClientSet) {
|
|
134
|
+func (clients *ClientManager) AllWithCaps(capabs ...caps.Capability) (set ClientSet) {
|
160
|
135
|
set = make(ClientSet)
|
161
|
136
|
|
162
|
|
- clients.ByNickMutex.RLock()
|
163
|
|
- defer clients.ByNickMutex.RUnlock()
|
|
137
|
+ clients.RLock()
|
|
138
|
+ defer clients.RUnlock()
|
164
|
139
|
var client *Client
|
165
|
|
- for _, client = range clients.ByNick {
|
|
140
|
+ for _, client = range clients.byNick {
|
166
|
141
|
// make sure they have all the required caps
|
167
|
142
|
for _, capab := range capabs {
|
168
|
143
|
if !client.capabilities.Has(capab) {
|
|
@@ -177,7 +152,7 @@ func (clients *ClientLookupSet) AllWithCaps(capabs ...caps.Capability) (set Clie
|
177
|
152
|
}
|
178
|
153
|
|
179
|
154
|
// FindAll returns all clients that match the given userhost mask.
|
180
|
|
-func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
|
|
155
|
+func (clients *ClientManager) FindAll(userhost string) (set ClientSet) {
|
181
|
156
|
set = make(ClientSet)
|
182
|
157
|
|
183
|
158
|
userhost, err := Casefold(ExpandUserHost(userhost))
|
|
@@ -186,9 +161,9 @@ func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
|
186
|
161
|
}
|
187
|
162
|
matcher := ircmatch.MakeMatch(userhost)
|
188
|
163
|
|
189
|
|
- clients.ByNickMutex.RLock()
|
190
|
|
- defer clients.ByNickMutex.RUnlock()
|
191
|
|
- for _, client := range clients.ByNick {
|
|
164
|
+ clients.RLock()
|
|
165
|
+ defer clients.RUnlock()
|
|
166
|
+ for _, client := range clients.byNick {
|
192
|
167
|
if matcher.Match(client.nickMaskCasefolded) {
|
193
|
168
|
set.Add(client)
|
194
|
169
|
}
|
|
@@ -198,7 +173,7 @@ func (clients *ClientLookupSet) FindAll(userhost string) (set ClientSet) {
|
198
|
173
|
}
|
199
|
174
|
|
200
|
175
|
// Find returns the first client that matches the given userhost mask.
|
201
|
|
-func (clients *ClientLookupSet) Find(userhost string) *Client {
|
|
176
|
+func (clients *ClientManager) Find(userhost string) *Client {
|
202
|
177
|
userhost, err := Casefold(ExpandUserHost(userhost))
|
203
|
178
|
if err != nil {
|
204
|
179
|
return nil
|
|
@@ -206,9 +181,9 @@ func (clients *ClientLookupSet) Find(userhost string) *Client {
|
206
|
181
|
matcher := ircmatch.MakeMatch(userhost)
|
207
|
182
|
var matchedClient *Client
|
208
|
183
|
|
209
|
|
- clients.ByNickMutex.RLock()
|
210
|
|
- defer clients.ByNickMutex.RUnlock()
|
211
|
|
- for _, client := range clients.ByNick {
|
|
184
|
+ clients.RLock()
|
|
185
|
+ defer clients.RUnlock()
|
|
186
|
+ for _, client := range clients.byNick {
|
212
|
187
|
if matcher.Match(client.nickMaskCasefolded) {
|
213
|
188
|
matchedClient = client
|
214
|
189
|
break
|