1package state 2 3import ( 4 "github.com/fluffle/goirc/logging" 5 6 "sync" 7) 8 9// The state manager interface 10type Tracker interface { 11 // Nick methods 12 NewNick(nick string) *Nick 13 GetNick(nick string) *Nick 14 ReNick(old, neu string) *Nick 15 DelNick(nick string) *Nick 16 NickInfo(nick, ident, host, name string) *Nick 17 NickModes(nick, modestr string) *Nick 18 // Channel methods 19 NewChannel(channel string) *Channel 20 GetChannel(channel string) *Channel 21 DelChannel(channel string) *Channel 22 Topic(channel, topic string) *Channel 23 ChannelModes(channel, modestr string, modeargs ...string) *Channel 24 // Information about ME! 25 Me() *Nick 26 // And the tracking operations 27 IsOn(channel, nick string) (*ChanPrivs, bool) 28 Associate(channel, nick string) *ChanPrivs 29 Dissociate(channel, nick string) 30 Wipe() 31 // The state tracker can output a debugging string 32 String() string 33} 34 35// ... and a struct to implement it ... 36type stateTracker struct { 37 // Map of channels we're on 38 chans map[string]*channel 39 // Map of nicks we know about 40 nicks map[string]*nick 41 42 // We need to keep state on who we are :-) 43 me *nick 44 45 // And we need to protect against data races *cough*. 46 mu sync.Mutex 47} 48 49var _ Tracker = (*stateTracker)(nil) 50 51// ... and a constructor to make it ... 52func NewTracker(mynick string) *stateTracker { 53 st := &stateTracker{ 54 chans: make(map[string]*channel), 55 nicks: make(map[string]*nick), 56 } 57 st.me = newNick(mynick) 58 st.nicks[mynick] = st.me 59 return st 60} 61 62// ... and a method to wipe the state clean. 63func (st *stateTracker) Wipe() { 64 st.mu.Lock() 65 defer st.mu.Unlock() 66 // Deleting all the channels implicitly deletes every nick but me. 67 for _, ch := range st.chans { 68 st.delChannel(ch) 69 } 70} 71 72/******************************************************************************\ 73 * tracker methods to create/look up nicks/channels 74\******************************************************************************/ 75 76// Creates a new nick, initialises it, and stores it so it 77// can be properly tracked for state management purposes. 78func (st *stateTracker) NewNick(n string) *Nick { 79 if n == "" { 80 logging.Warn("Tracker.NewNick(): Not tracking empty nick.") 81 return nil 82 } 83 st.mu.Lock() 84 defer st.mu.Unlock() 85 if _, ok := st.nicks[n]; ok { 86 logging.Warn("Tracker.NewNick(): %s already tracked.", n) 87 return nil 88 } 89 st.nicks[n] = newNick(n) 90 return st.nicks[n].Nick() 91} 92 93// Returns a nick for the nick n, if we're tracking it. 94func (st *stateTracker) GetNick(n string) *Nick { 95 st.mu.Lock() 96 defer st.mu.Unlock() 97 if nk, ok := st.nicks[n]; ok { 98 return nk.Nick() 99 } 100 return nil 101} 102 103// Signals to the tracker that a nick should be tracked 104// under a "neu" nick rather than the old one. 105func (st *stateTracker) ReNick(old, neu string) *Nick { 106 st.mu.Lock() 107 defer st.mu.Unlock() 108 nk, ok := st.nicks[old] 109 if !ok { 110 logging.Warn("Tracker.ReNick(): %s not tracked.", old) 111 return nil 112 } 113 if _, ok := st.nicks[neu]; ok { 114 logging.Warn("Tracker.ReNick(): %s already exists.", neu) 115 return nil 116 } 117 118 nk.nick = neu 119 delete(st.nicks, old) 120 st.nicks[neu] = nk 121 for ch, _ := range nk.chans { 122 // We also need to update the lookup maps of all the channels 123 // the nick is on, to keep things in sync. 124 delete(ch.lookup, old) 125 ch.lookup[neu] = nk 126 } 127 return nk.Nick() 128} 129 130// Removes a nick from being tracked. 131func (st *stateTracker) DelNick(n string) *Nick { 132 st.mu.Lock() 133 defer st.mu.Unlock() 134 if nk, ok := st.nicks[n]; ok { 135 if nk == st.me { 136 logging.Warn("Tracker.DelNick(): won't delete myself.") 137 return nil 138 } 139 st.delNick(nk) 140 return nk.Nick() 141 } 142 logging.Warn("Tracker.DelNick(): %s not tracked.", n) 143 return nil 144} 145 146func (st *stateTracker) delNick(nk *nick) { 147 // st.mu lock held by DelNick, DelChannel or Wipe 148 if nk == st.me { 149 // Shouldn't get here => internal state tracking code is fubar. 150 logging.Error("Tracker.DelNick(): TRYING TO DELETE ME :-(") 151 return 152 } 153 delete(st.nicks, nk.nick) 154 for ch, _ := range nk.chans { 155 nk.delChannel(ch) 156 ch.delNick(nk) 157 if len(ch.nicks) == 0 { 158 // Deleting a nick from tracking shouldn't empty any channels as 159 // *we* should be on the channel with them to be tracking them. 160 logging.Error("Tracker.delNick(): deleting nick %s emptied "+ 161 "channel %s, this shouldn't happen!", nk.nick, ch.name) 162 } 163 } 164} 165 166// Sets ident, host and "real" name for the nick. 167func (st *stateTracker) NickInfo(n, ident, host, name string) *Nick { 168 st.mu.Lock() 169 defer st.mu.Unlock() 170 nk, ok := st.nicks[n] 171 if !ok { 172 return nil 173 } 174 nk.ident = ident 175 nk.host = host 176 nk.name = name 177 return nk.Nick() 178} 179 180// Sets user modes for the nick. 181func (st *stateTracker) NickModes(n, modes string) *Nick { 182 st.mu.Lock() 183 defer st.mu.Unlock() 184 nk, ok := st.nicks[n] 185 if !ok { 186 return nil 187 } 188 nk.parseModes(modes) 189 return nk.Nick() 190} 191 192// Creates a new Channel, initialises it, and stores it so it 193// can be properly tracked for state management purposes. 194func (st *stateTracker) NewChannel(c string) *Channel { 195 if c == "" { 196 logging.Warn("Tracker.NewChannel(): Not tracking empty channel.") 197 return nil 198 } 199 st.mu.Lock() 200 defer st.mu.Unlock() 201 if _, ok := st.chans[c]; ok { 202 logging.Warn("Tracker.NewChannel(): %s already tracked.", c) 203 return nil 204 } 205 st.chans[c] = newChannel(c) 206 return st.chans[c].Channel() 207} 208 209// Returns a Channel for the channel c, if we're tracking it. 210func (st *stateTracker) GetChannel(c string) *Channel { 211 st.mu.Lock() 212 defer st.mu.Unlock() 213 if ch, ok := st.chans[c]; ok { 214 return ch.Channel() 215 } 216 return nil 217} 218 219// Removes a Channel from being tracked. 220func (st *stateTracker) DelChannel(c string) *Channel { 221 st.mu.Lock() 222 defer st.mu.Unlock() 223 if ch, ok := st.chans[c]; ok { 224 st.delChannel(ch) 225 return ch.Channel() 226 } 227 logging.Warn("Tracker.DelChannel(): %s not tracked.", c) 228 return nil 229} 230 231func (st *stateTracker) delChannel(ch *channel) { 232 // st.mu lock held by DelChannel or Wipe 233 delete(st.chans, ch.name) 234 for nk, _ := range ch.nicks { 235 ch.delNick(nk) 236 nk.delChannel(ch) 237 if len(nk.chans) == 0 && nk != st.me { 238 // We're no longer in any channels with this nick. 239 st.delNick(nk) 240 } 241 } 242} 243 244// Sets the topic of a channel. 245func (st *stateTracker) Topic(c, topic string) *Channel { 246 st.mu.Lock() 247 defer st.mu.Unlock() 248 ch, ok := st.chans[c] 249 if !ok { 250 return nil 251 } 252 ch.topic = topic 253 return ch.Channel() 254} 255 256// Sets modes for a channel, including privileges like +o. 257func (st *stateTracker) ChannelModes(c, modes string, args ...string) *Channel { 258 st.mu.Lock() 259 defer st.mu.Unlock() 260 ch, ok := st.chans[c] 261 if !ok { 262 return nil 263 } 264 ch.parseModes(modes, args...) 265 return ch.Channel() 266} 267 268// Returns the Nick the state tracker thinks is Me. 269// NOTE: Nick() requires the mutex to be held. 270func (st *stateTracker) Me() *Nick { 271 st.mu.Lock() 272 defer st.mu.Unlock() 273 return st.me.Nick() 274} 275 276// Returns true if both the channel c and the nick n are tracked 277// and the nick is associated with the channel. 278func (st *stateTracker) IsOn(c, n string) (*ChanPrivs, bool) { 279 st.mu.Lock() 280 defer st.mu.Unlock() 281 nk, nok := st.nicks[n] 282 ch, cok := st.chans[c] 283 if nok && cok { 284 return nk.isOn(ch) 285 } 286 return nil, false 287} 288 289// Associates an already known nick with an already known channel. 290func (st *stateTracker) Associate(c, n string) *ChanPrivs { 291 st.mu.Lock() 292 defer st.mu.Unlock() 293 nk, nok := st.nicks[n] 294 ch, cok := st.chans[c] 295 296 if !cok { 297 // As we can implicitly delete both nicks and channels from being 298 // tracked by dissociating one from the other, we should verify that 299 // we're not being passed an old Nick or Channel. 300 logging.Error("Tracker.Associate(): channel %s not found in "+ 301 "internal state.", c) 302 return nil 303 } else if !nok { 304 logging.Error("Tracker.Associate(): nick %s not found in "+ 305 "internal state.", n) 306 return nil 307 } else if _, ok := nk.isOn(ch); ok { 308 logging.Warn("Tracker.Associate(): %s already on %s.", 309 nk, ch) 310 return nil 311 } 312 cp := new(ChanPrivs) 313 ch.addNick(nk, cp) 314 nk.addChannel(ch, cp) 315 return cp.Copy() 316} 317 318// Dissociates an already known nick from an already known channel. 319// Does some tidying up to stop tracking nicks we're no longer on 320// any common channels with, and channels we're no longer on. 321func (st *stateTracker) Dissociate(c, n string) { 322 st.mu.Lock() 323 defer st.mu.Unlock() 324 nk, nok := st.nicks[n] 325 ch, cok := st.chans[c] 326 327 if !cok { 328 // As we can implicitly delete both nicks and channels from being 329 // tracked by dissociating one from the other, we should verify that 330 // we're not being passed an old Nick or Channel. 331 logging.Error("Tracker.Dissociate(): channel %s not found in "+ 332 "internal state.", c) 333 } else if !nok { 334 logging.Error("Tracker.Dissociate(): nick %s not found in "+ 335 "internal state.", n) 336 } else if _, ok := nk.isOn(ch); !ok { 337 logging.Warn("Tracker.Dissociate(): %s not on %s.", 338 nk.nick, ch.name) 339 } else if nk == st.me { 340 // I'm leaving the channel for some reason, so it won't be tracked. 341 st.delChannel(ch) 342 } else { 343 // Remove the nick from the channel and the channel from the nick. 344 ch.delNick(nk) 345 nk.delChannel(ch) 346 if len(nk.chans) == 0 { 347 // We're no longer in any channels with this nick. 348 st.delNick(nk) 349 } 350 } 351} 352 353func (st *stateTracker) String() string { 354 st.mu.Lock() 355 defer st.mu.Unlock() 356 str := "GoIRC Channels\n" 357 str += "--------------\n\n" 358 for _, ch := range st.chans { 359 str += ch.String() + "\n" 360 } 361 str += "GoIRC NickNames\n" 362 str += "---------------\n\n" 363 for _, n := range st.nicks { 364 if n != st.me { 365 str += n.String() + "\n" 366 } 367 } 368 return str 369} 370