diff --git a/irc/channel.go b/irc/channel.go index cbc837a2..fed224aa 100644 --- a/irc/channel.go +++ b/irc/channel.go @@ -2,21 +2,18 @@ package irc import ( "log" - "sync" ) type Channel struct { - banList []UserMask - commands chan<- ChannelCommand - destroyed bool - flags map[ChannelMode]bool - key string - members ClientSet - mutex *sync.Mutex - name string - replies chan<- Reply - server *Server - topic string + banList []UserMask + commands chan<- ChannelCommand + flags ChannelModeSet + key string + members MemberSet + name string + replies chan<- Reply + server *Server + topic string } func IsChannel(target string) bool { @@ -38,9 +35,8 @@ func NewChannel(s *Server, name string) *Channel { channel := &Channel{ banList: make([]UserMask, 0), commands: commands, - flags: make(map[ChannelMode]bool), - members: make(ClientSet), - mutex: &sync.Mutex{}, + flags: make(ChannelModeSet), + members: make(MemberSet), name: name, replies: replies, server: s, @@ -50,19 +46,17 @@ func NewChannel(s *Server, name string) *Channel { return channel } +type DestroyChannel struct { + BaseCommand + channel *Channel +} + func (channel *Channel) Destroy() { - if channel.IsDestroyed() { - return - } - - channel.withMutex(func() { - channel.destroyed = true - channel.members = make(ClientSet) - }) - - channel.server.withMutex(func() { - channel.server.channels.Remove(channel) + channel.server.Command(&DestroyChannel{ + channel: channel, }) + close(channel.commands) + close(channel.replies) } func (channel *Channel) Command(command ChannelCommand) { @@ -75,13 +69,6 @@ func (channel *Channel) Reply(reply Reply) { func (channel *Channel) receiveCommands(commands <-chan ChannelCommand) { for command := range commands { - if channel.IsDestroyed() { - if DEBUG_CHANNEL { - log.Printf("%s → %s %s dropped", command.Source(), channel, command) - } - continue - } - if DEBUG_CHANNEL { log.Printf("%s → %s %s", command.Source(), channel, command) } @@ -89,40 +76,19 @@ func (channel *Channel) receiveCommands(commands <-chan ChannelCommand) { } } -func IsPrivMsg(reply Reply) bool { - strReply, ok := reply.(*StringReply) - if !ok { - return false - } - return strReply.code == "PRIVMSG" -} - -func (channel *Channel) IsDestroyed() bool { - channel.mutex.Lock() - defer channel.mutex.Unlock() - return channel.destroyed -} - func (channel *Channel) receiveReplies(replies <-chan Reply) { for reply := range replies { - if channel.IsDestroyed() { - if DEBUG_CHANNEL { - log.Printf("%s ← %s %s dropped", channel, reply.Source(), reply) - } - continue - } - if DEBUG_CHANNEL { log.Printf("%s ← %s %s", channel, reply.Source(), reply) } - channel.withMutex(func() { - for client := range channel.members { - if IsPrivMsg(reply) && (reply.Source() == Identifier(client)) { - continue - } - client.Reply(reply) + + for client := range channel.members { + if (reply.Code() == ReplyCode(PRIVMSG)) && + (reply.Source() == Identifier(client)) { + continue } - }) + client.Reply(reply) + } } } @@ -198,25 +164,9 @@ func (channel *Channel) ModeString() (str string) { return } -func (channel *Channel) withMutex(f func()) { - channel.mutex.Lock() - defer channel.mutex.Unlock() - f() -} - -func (channel *Channel) Join(client *Client) { - channel.withMutex(func() { - channel.members.Add(client) - if len(channel.members) == 1 { - channel.members[client][ChannelCreator] = true - channel.members[client][ChannelOperator] = true - } - client.channels.Add(channel) - }) - - channel.Reply(RplJoin(client, channel)) - channel.GetTopic(client) - channel.GetUsers(client) +type JoinChannel struct { + BaseCommand + channel *Channel } // @@ -230,7 +180,33 @@ func (m *JoinCommand) HandleChannel(channel *Channel) { return } - channel.Join(client) + channel.members.Add(client) + if len(channel.members) == 1 { + channel.members[client][ChannelCreator] = true + channel.members[client][ChannelOperator] = true + } + + client.commands <- &JoinChannel{ + channel: channel, + } + + addClient := &AddFriend{ + client: client, + } + for member := range channel.members { + client.commands <- &AddFriend{ + client: member, + } + member.commands <- addClient + } + + channel.Reply(RplJoin(client, channel)) + channel.GetTopic(client) + channel.GetUsers(client) +} + +type PartChannel struct { + channel *Channel } func (m *PartCommand) HandleChannel(channel *Channel) { @@ -244,9 +220,15 @@ func (m *PartCommand) HandleChannel(channel *Channel) { channel.Reply(RplPart(client, channel, m.Message())) channel.members.Remove(client) - client.channels.Remove(channel) + client.commands <- &PartChannel{ + channel: channel, + } + for member := range channel.members { + member.commands <- &RemoveFriend{ + client: client, + } + } - // TODO persistent channels if channel.IsEmpty() { channel.Destroy() } @@ -387,3 +369,18 @@ func (m *NoticeCommand) HandleChannel(channel *Channel) { } channel.Reply(RplNotice(client, channel, m.message)) } + +func (msg *QuitCommand) HandleChannel(channel *Channel) { + client := msg.Client() + removeClient := &RemoveFriend{ + client: client, + } + for member := range channel.members { + member.commands <- removeClient + } + channel.members.Remove(client) +} + +func (msg *DestroyClient) HandleChannel(channel *Channel) { + channel.members.Remove(msg.client) +} diff --git a/irc/client.go b/irc/client.go index 04b5bc1a..a7887b6b 100644 --- a/irc/client.go +++ b/irc/client.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "net" - "sync" "time" ) @@ -13,13 +12,13 @@ type Client struct { away bool awayMessage string channels ChannelSet + commands chan ClientCommand ctime time.Time - destroyed bool + friends map[*Client]uint hostname string idleTimer *time.Timer invisible bool loginTimer *time.Timer - mutex *sync.Mutex nick string operator bool phase Phase @@ -36,16 +35,18 @@ func NewClient(server *Server, conn net.Conn) *Client { client := &Client{ atime: now, channels: make(ChannelSet), + commands: make(chan ClientCommand), ctime: now, + friends: make(map[*Client]uint), hostname: AddrLookupHostname(conn.RemoteAddr()), phase: server.InitPhase(), replies: make(chan Reply), server: server, socket: NewSocket(conn), - mutex: &sync.Mutex{}, } - client.loginTimer = time.AfterFunc(LOGIN_TIMEOUT, client.Destroy) + client.loginTimer = time.AfterFunc(LOGIN_TIMEOUT, client.Destroy) + go client.readClientCommands() go client.readCommands() go client.writeReplies() @@ -53,10 +54,6 @@ func NewClient(server *Server, conn net.Conn) *Client { } func (client *Client) Touch() { - if client.IsDestroyed() { - return - } - client.atime = time.Now() if client.quitTimer != nil { @@ -89,10 +86,6 @@ func (client *Client) ConnectionTimeout() { } func (client *Client) ConnectionClosed() { - if client.IsDestroyed() { - return - } - msg := &QuitCommand{ message: "connection closed", } @@ -100,6 +93,12 @@ func (client *Client) ConnectionClosed() { client.server.Command(msg) } +func (client *Client) readClientCommands() { + for command := range client.commands { + command.HandleClient(client) + } +} + func (c *Client) readCommands() { for line := range c.socket.Read() { m, err := ParseCommand(line) @@ -126,13 +125,6 @@ func (c *Client) readCommands() { func (client *Client) writeReplies() { for reply := range client.replies { - if client.IsDestroyed() { - if DEBUG_CLIENT { - log.Printf("%s ← %s dropped", client, reply) - } - continue - } - if DEBUG_CLIENT { log.Printf("%s ← %s", client, reply) } @@ -143,24 +135,12 @@ func (client *Client) writeReplies() { } } -func (client *Client) IsDestroyed() bool { - client.mutex.Lock() - defer client.mutex.Unlock() - return client.destroyed +type DestroyClient struct { + BaseCommand + client *Client } func (client *Client) Destroy() { - if client.IsDestroyed() { - return - } - - if DEBUG_CLIENT { - log.Printf("%s destroying", client) - } - - client.mutex.Lock() - client.destroyed = true - client.socket.Close() if client.idleTimer != nil { @@ -171,14 +151,15 @@ func (client *Client) Destroy() { client.quitTimer.Stop() } - client.channels = make(ChannelSet) // clear channel list - client.server.clients.Remove(client) - - client.mutex.Unlock() - - if DEBUG_CLIENT { - log.Printf("%s destroyed", client) + cmd := &DestroyClient{ + client: client, } + + for channel := range client.channels { + channel.Command(cmd) + } + + client.server.Command(cmd) } func (client *Client) Reply(reply Reply) { @@ -199,16 +180,6 @@ func (client *Client) HasUsername() bool { return client.username != "" } -func (client *Client) InterestedClients() ClientSet { - clients := make(ClientSet) - for channel := range client.channels { - for member := range channel.members { - clients.Add(member) - } - } - return clients -} - // func (c *Client) ModeString() (str string) { if c.invisible { @@ -246,3 +217,63 @@ func (c *Client) Id() string { func (c *Client) String() string { return c.UserHost() } + +// +// commands +// + +type AddFriend struct { + client *Client +} + +func (msg *AddFriend) HandleClient(client *Client) { + client.friends[msg.client] += 1 +} + +type RemoveFriend struct { + client *Client +} + +func (msg *RemoveFriend) HandleClient(client *Client) { + client.friends[msg.client] -= 1 + if client.friends[msg.client] <= 0 { + delete(client.friends, msg.client) + } +} + +func (msg *JoinChannel) HandleClient(client *Client) { + client.channels.Add(msg.channel) +} + +func (msg *PartChannel) HandleClient(client *Client) { + client.channels.Remove(msg.channel) +} + +func (msg *NickCommand) HandleClient(client *Client) { + // Make reply before changing nick. + reply := RplNick(client, msg.nickname) + + client.nick = msg.nickname + + for friend := range client.friends { + friend.Reply(reply) + } +} + +func (msg *QuitCommand) HandleClient(client *Client) { + if len(client.friends) > 0 { + reply := RplQuit(client, msg.message) + for friend := range client.friends { + if friend == client { + continue + } + friend.Reply(reply) + } + } + + for channel := range client.channels { + channel.commands <- msg + } + + client.Destroy() +} diff --git a/irc/constants.go b/irc/constants.go index 0ef8bd87..710c3398 100644 --- a/irc/constants.go +++ b/irc/constants.go @@ -23,143 +23,157 @@ const ( IDLE_TIMEOUT = time.Minute // how long before a client is considered idle QUIT_TIMEOUT = time.Minute // how long after idle before a client is kicked + // string codes + PRIVMSG StringCode = "PRIVMSG" + NOTICE StringCode = "NOTICE" + NICK StringCode = "NICK" + JOIN StringCode = "JOIN" + PART StringCode = "PART" + MODE StringCode = "MODE" + TOPIC StringCode = "TOPIC" + PING StringCode = "PING" + PONG StringCode = "PONG" + QUIT StringCode = "QUIT" + ERROR StringCode = "ERROR" + INVITE StringCode = "INVITE" + // numeric codes - RPL_WELCOME Numeric = 1 - RPL_YOURHOST Numeric = 2 - RPL_CREATED Numeric = 3 - RPL_MYINFO Numeric = 4 - RPL_BOUNCE Numeric = 5 - RPL_TRACELINK Numeric = 200 - RPL_TRACECONNECTING Numeric = 201 - RPL_TRACEHANDSHAKE Numeric = 202 - RPL_TRACEUNKNOWN Numeric = 203 - RPL_TRACEOPERATOR Numeric = 204 - RPL_TRACEUSER Numeric = 205 - RPL_TRACESERVER Numeric = 206 - RPL_TRACESERVICE Numeric = 207 - RPL_TRACENEWTYPE Numeric = 208 - RPL_TRACECLASS Numeric = 209 - RPL_TRACERECONNECT Numeric = 210 - RPL_STATSLINKINFO Numeric = 211 - RPL_STATSCOMMANDS Numeric = 212 - RPL_ENDOFSTATS Numeric = 219 - RPL_UMODEIS Numeric = 221 - RPL_SERVLIST Numeric = 234 - RPL_SERVLISTEND Numeric = 235 - RPL_STATSUPTIME Numeric = 242 - RPL_STATSOLINE Numeric = 243 - RPL_LUSERCLIENT Numeric = 251 - RPL_LUSEROP Numeric = 252 - RPL_LUSERUNKNOWN Numeric = 253 - RPL_LUSERCHANNELS Numeric = 254 - RPL_LUSERME Numeric = 255 - RPL_ADMINME Numeric = 256 - RPL_ADMINLOC1 Numeric = 257 - RPL_ADMINLOC2 Numeric = 258 - RPL_ADMINEMAIL Numeric = 259 - RPL_TRACELOG Numeric = 261 - RPL_TRACEEND Numeric = 262 - RPL_TRYAGAIN Numeric = 263 - RPL_AWAY Numeric = 301 - RPL_USERHOST Numeric = 302 - RPL_ISON Numeric = 303 - RPL_UNAWAY Numeric = 305 - RPL_NOWAWAY Numeric = 306 - RPL_WHOISUSER Numeric = 311 - RPL_WHOISSERVER Numeric = 312 - RPL_WHOISOPERATOR Numeric = 313 - RPL_WHOWASUSER Numeric = 314 - RPL_ENDOFWHO Numeric = 315 - RPL_WHOISIDLE Numeric = 317 - RPL_ENDOFWHOIS Numeric = 318 - RPL_WHOISCHANNELS Numeric = 319 - RPL_LIST Numeric = 322 - RPL_LISTEND Numeric = 323 - RPL_CHANNELMODEIS Numeric = 324 - RPL_UNIQOPIS Numeric = 325 - RPL_NOTOPIC Numeric = 331 - RPL_TOPIC Numeric = 332 - RPL_INVITING Numeric = 341 - RPL_SUMMONING Numeric = 342 - RPL_INVITELIST Numeric = 346 - RPL_ENDOFINVITELIST Numeric = 347 - RPL_EXCEPTLIST Numeric = 348 - RPL_ENDOFEXCEPTLIST Numeric = 349 - RPL_VERSION Numeric = 351 - RPL_WHOREPLY Numeric = 352 - RPL_NAMREPLY Numeric = 353 - RPL_LINKS Numeric = 364 - RPL_ENDOFLINKS Numeric = 365 - RPL_ENDOFNAMES Numeric = 366 - RPL_BANLIST Numeric = 367 - RPL_ENDOFBANLIST Numeric = 368 - RPL_ENDOFWHOWAS Numeric = 369 - RPL_INFO Numeric = 371 - RPL_MOTD Numeric = 372 - RPL_ENDOFINFO Numeric = 374 - RPL_MOTDSTART Numeric = 375 - RPL_ENDOFMOTD Numeric = 376 - RPL_YOUREOPER Numeric = 381 - RPL_REHASHING Numeric = 382 - RPL_YOURESERVICE Numeric = 383 - RPL_TIME Numeric = 391 - RPL_USERSSTART Numeric = 392 - RPL_USERS Numeric = 393 - RPL_ENDOFUSERS Numeric = 394 - RPL_NOUSERS Numeric = 395 - ERR_NOSUCHNICK Numeric = 401 - ERR_NOSUCHSERVER Numeric = 402 - ERR_NOSUCHCHANNEL Numeric = 403 - ERR_CANNOTSENDTOCHAN Numeric = 404 - ERR_TOOMANYCHANNELS Numeric = 405 - ERR_WASNOSUCHNICK Numeric = 406 - ERR_TOOMANYTARGETS Numeric = 407 - ERR_NOSUCHSERVICE Numeric = 408 - ERR_NOORIGIN Numeric = 409 - ERR_NORECIPIENT Numeric = 411 - ERR_NOTEXTTOSEND Numeric = 412 - ERR_NOTOPLEVEL Numeric = 413 - ERR_WILDTOPLEVEL Numeric = 414 - ERR_BADMASK Numeric = 415 - ERR_UNKNOWNCOMMAND Numeric = 421 - ERR_NOMOTD Numeric = 422 - ERR_NOADMININFO Numeric = 423 - ERR_FILEERROR Numeric = 424 - ERR_NONICKNAMEGIVEN Numeric = 431 - ERR_ERRONEUSNICKNAME Numeric = 432 - ERR_NICKNAMEINUSE Numeric = 433 - ERR_NICKCOLLISION Numeric = 436 - ERR_UNAVAILRESOURCE Numeric = 437 - ERR_USERNOTINCHANNEL Numeric = 441 - ERR_NOTONCHANNEL Numeric = 442 - ERR_USERONCHANNEL Numeric = 443 - ERR_NOLOGIN Numeric = 444 - ERR_SUMMONDISABLED Numeric = 445 - ERR_USERSDISABLED Numeric = 446 - ERR_NOTREGISTERED Numeric = 451 - ERR_NEEDMOREPARAMS Numeric = 461 - ERR_ALREADYREGISTRED Numeric = 462 - ERR_NOPERMFORHOST Numeric = 463 - ERR_PASSWDMISMATCH Numeric = 464 - ERR_YOUREBANNEDCREEP Numeric = 465 - ERR_YOUWILLBEBANNED Numeric = 466 - ERR_KEYSET Numeric = 467 - ERR_CHANNELISFULL Numeric = 471 - ERR_UNKNOWNMODE Numeric = 472 - ERR_INVITEONLYCHAN Numeric = 473 - ERR_BANNEDFROMCHAN Numeric = 474 - ERR_BADCHANNELKEY Numeric = 475 - ERR_BADCHANMASK Numeric = 476 - ERR_NOCHANMODES Numeric = 477 - ERR_BANLISTFULL Numeric = 478 - ERR_NOPRIVILEGES Numeric = 481 - ERR_CHANOPRIVSNEEDED Numeric = 482 - ERR_CANTKILLSERVER Numeric = 483 - ERR_RESTRICTED Numeric = 484 - ERR_UNIQOPPRIVSNEEDED Numeric = 485 - ERR_NOOPERHOST Numeric = 491 - ERR_UMODEUNKNOWNFLAG Numeric = 501 - ERR_USERSDONTMATCH Numeric = 502 + RPL_WELCOME NumericCode = 1 + RPL_YOURHOST NumericCode = 2 + RPL_CREATED NumericCode = 3 + RPL_MYINFO NumericCode = 4 + RPL_BOUNCE NumericCode = 5 + RPL_TRACELINK NumericCode = 200 + RPL_TRACECONNECTING NumericCode = 201 + RPL_TRACEHANDSHAKE NumericCode = 202 + RPL_TRACEUNKNOWN NumericCode = 203 + RPL_TRACEOPERATOR NumericCode = 204 + RPL_TRACEUSER NumericCode = 205 + RPL_TRACESERVER NumericCode = 206 + RPL_TRACESERVICE NumericCode = 207 + RPL_TRACENEWTYPE NumericCode = 208 + RPL_TRACECLASS NumericCode = 209 + RPL_TRACERECONNECT NumericCode = 210 + RPL_STATSLINKINFO NumericCode = 211 + RPL_STATSCOMMANDS NumericCode = 212 + RPL_ENDOFSTATS NumericCode = 219 + RPL_UMODEIS NumericCode = 221 + RPL_SERVLIST NumericCode = 234 + RPL_SERVLISTEND NumericCode = 235 + RPL_STATSUPTIME NumericCode = 242 + RPL_STATSOLINE NumericCode = 243 + RPL_LUSERCLIENT NumericCode = 251 + RPL_LUSEROP NumericCode = 252 + RPL_LUSERUNKNOWN NumericCode = 253 + RPL_LUSERCHANNELS NumericCode = 254 + RPL_LUSERME NumericCode = 255 + RPL_ADMINME NumericCode = 256 + RPL_ADMINLOC1 NumericCode = 257 + RPL_ADMINLOC2 NumericCode = 258 + RPL_ADMINEMAIL NumericCode = 259 + RPL_TRACELOG NumericCode = 261 + RPL_TRACEEND NumericCode = 262 + RPL_TRYAGAIN NumericCode = 263 + RPL_AWAY NumericCode = 301 + RPL_USERHOST NumericCode = 302 + RPL_ISON NumericCode = 303 + RPL_UNAWAY NumericCode = 305 + RPL_NOWAWAY NumericCode = 306 + RPL_WHOISUSER NumericCode = 311 + RPL_WHOISSERVER NumericCode = 312 + RPL_WHOISOPERATOR NumericCode = 313 + RPL_WHOWASUSER NumericCode = 314 + RPL_ENDOFWHO NumericCode = 315 + RPL_WHOISIDLE NumericCode = 317 + RPL_ENDOFWHOIS NumericCode = 318 + RPL_WHOISCHANNELS NumericCode = 319 + RPL_LIST NumericCode = 322 + RPL_LISTEND NumericCode = 323 + RPL_CHANNELMODEIS NumericCode = 324 + RPL_UNIQOPIS NumericCode = 325 + RPL_NOTOPIC NumericCode = 331 + RPL_TOPIC NumericCode = 332 + RPL_INVITING NumericCode = 341 + RPL_SUMMONING NumericCode = 342 + RPL_INVITELIST NumericCode = 346 + RPL_ENDOFINVITELIST NumericCode = 347 + RPL_EXCEPTLIST NumericCode = 348 + RPL_ENDOFEXCEPTLIST NumericCode = 349 + RPL_VERSION NumericCode = 351 + RPL_WHOREPLY NumericCode = 352 + RPL_NAMREPLY NumericCode = 353 + RPL_LINKS NumericCode = 364 + RPL_ENDOFLINKS NumericCode = 365 + RPL_ENDOFNAMES NumericCode = 366 + RPL_BANLIST NumericCode = 367 + RPL_ENDOFBANLIST NumericCode = 368 + RPL_ENDOFWHOWAS NumericCode = 369 + RPL_INFO NumericCode = 371 + RPL_MOTD NumericCode = 372 + RPL_ENDOFINFO NumericCode = 374 + RPL_MOTDSTART NumericCode = 375 + RPL_ENDOFMOTD NumericCode = 376 + RPL_YOUREOPER NumericCode = 381 + RPL_REHASHING NumericCode = 382 + RPL_YOURESERVICE NumericCode = 383 + RPL_TIME NumericCode = 391 + RPL_USERSSTART NumericCode = 392 + RPL_USERS NumericCode = 393 + RPL_ENDOFUSERS NumericCode = 394 + RPL_NOUSERS NumericCode = 395 + ERR_NOSUCHNICK NumericCode = 401 + ERR_NOSUCHSERVER NumericCode = 402 + ERR_NOSUCHCHANNEL NumericCode = 403 + ERR_CANNOTSENDTOCHAN NumericCode = 404 + ERR_TOOMANYCHANNELS NumericCode = 405 + ERR_WASNOSUCHNICK NumericCode = 406 + ERR_TOOMANYTARGETS NumericCode = 407 + ERR_NOSUCHSERVICE NumericCode = 408 + ERR_NOORIGIN NumericCode = 409 + ERR_NORECIPIENT NumericCode = 411 + ERR_NOTEXTTOSEND NumericCode = 412 + ERR_NOTOPLEVEL NumericCode = 413 + ERR_WILDTOPLEVEL NumericCode = 414 + ERR_BADMASK NumericCode = 415 + ERR_UNKNOWNCOMMAND NumericCode = 421 + ERR_NOMOTD NumericCode = 422 + ERR_NOADMININFO NumericCode = 423 + ERR_FILEERROR NumericCode = 424 + ERR_NONICKNAMEGIVEN NumericCode = 431 + ERR_ERRONEUSNICKNAME NumericCode = 432 + ERR_NICKNAMEINUSE NumericCode = 433 + ERR_NICKCOLLISION NumericCode = 436 + ERR_UNAVAILRESOURCE NumericCode = 437 + ERR_USERNOTINCHANNEL NumericCode = 441 + ERR_NOTONCHANNEL NumericCode = 442 + ERR_USERONCHANNEL NumericCode = 443 + ERR_NOLOGIN NumericCode = 444 + ERR_SUMMONDISABLED NumericCode = 445 + ERR_USERSDISABLED NumericCode = 446 + ERR_NOTREGISTERED NumericCode = 451 + ERR_NEEDMOREPARAMS NumericCode = 461 + ERR_ALREADYREGISTRED NumericCode = 462 + ERR_NOPERMFORHOST NumericCode = 463 + ERR_PASSWDMISMATCH NumericCode = 464 + ERR_YOUREBANNEDCREEP NumericCode = 465 + ERR_YOUWILLBEBANNED NumericCode = 466 + ERR_KEYSET NumericCode = 467 + ERR_CHANNELISFULL NumericCode = 471 + ERR_UNKNOWNMODE NumericCode = 472 + ERR_INVITEONLYCHAN NumericCode = 473 + ERR_BANNEDFROMCHAN NumericCode = 474 + ERR_BADCHANNELKEY NumericCode = 475 + ERR_BADCHANMASK NumericCode = 476 + ERR_NOCHANMODES NumericCode = 477 + ERR_BANLISTFULL NumericCode = 478 + ERR_NOPRIVILEGES NumericCode = 481 + ERR_CHANOPRIVSNEEDED NumericCode = 482 + ERR_CANTKILLSERVER NumericCode = 483 + ERR_RESTRICTED NumericCode = 484 + ERR_UNIQOPPRIVSNEEDED NumericCode = 485 + ERR_NOOPERHOST NumericCode = 491 + ERR_UMODEUNKNOWNFLAG NumericCode = 501 + ERR_USERSDONTMATCH NumericCode = 502 Add ModeOp = '+' List ModeOp = '=' diff --git a/irc/reply.go b/irc/reply.go index 54d2be27..a0cee495 100644 --- a/irc/reply.go +++ b/irc/reply.go @@ -15,11 +15,16 @@ func joinedLen(names []string) int { } type BaseReply struct { + code ReplyCode id string message string source Identifier } +func (reply *BaseReply) Code() ReplyCode { + return reply.code +} + func (reply *BaseReply) SetSource(source Identifier) { reply.id = source.Id() reply.source = source @@ -31,16 +36,14 @@ func (reply *BaseReply) Source() Identifier { type StringReply struct { BaseReply - code string } -func NewStringReply(source Identifier, code string, +func NewStringReply(source Identifier, code StringCode, format string, args ...interface{}) *StringReply { - reply := &StringReply{ - code: code, - } - reply.SetSource(source) + reply := &StringReply{} + reply.code = code reply.message = fmt.Sprintf(format, args...) + reply.SetSource(source) return reply } @@ -57,16 +60,14 @@ func (reply *StringReply) String() string { type NumericReply struct { BaseReply - code Numeric } -func NewNumericReply(source Identifier, code Numeric, format string, +func NewNumericReply(source Identifier, code NumericCode, format string, args ...interface{}) *NumericReply { - reply := &NumericReply{ - code: code, - } - reply.SetSource(source) + reply := &NumericReply{} + reply.code = code reply.message = fmt.Sprintf(format, args...) + reply.SetSource(source) return reply } @@ -92,7 +93,7 @@ func NewNamesReply(channel *Channel) Reply { reply := &NamesReply{ channel: channel, } - reply.SetSource(channel) + reply.SetSource(channel.server) return reply } @@ -107,14 +108,16 @@ func (reply *NamesReply) Format(client *Client) []string { nicks := reply.channel.Nicks() for to < len(nicks) { if (from < (to - 1)) && tooLong(nicks[from:to]) { - lines = append(lines, RplNamReply(reply.channel, nicks[from:to-1]).Format(client)...) + lines = append(lines, RplNamReply(reply.channel, + nicks[from:to-1]).Format(client)...) from, to = to-1, to } else { to += 1 } } if from < len(nicks) { - lines = append(lines, RplNamReply(reply.channel, nicks[from:]).Format(client)...) + lines = append(lines, RplNamReply(reply.channel, + nicks[from:]).Format(client)...) } lines = append(lines, RplEndOfNames(reply.channel).Format(client)...) return lines @@ -128,56 +131,56 @@ func (reply *NamesReply) String() string { // messaging replies func RplPrivMsg(source Identifier, target Identifier, message string) Reply { - return NewStringReply(source, "PRIVMSG", "%s :%s", target.Nick(), message) + return NewStringReply(source, PRIVMSG, "%s :%s", target.Nick(), message) } func RplNotice(source Identifier, target Identifier, message string) Reply { - return NewStringReply(source, "NOTICE", "%s :%s", target.Nick(), message) + return NewStringReply(source, NOTICE, "%s :%s", target.Nick(), message) } func RplNick(source Identifier, newNick string) Reply { - return NewStringReply(source, "NICK", newNick) + return NewStringReply(source, NICK, newNick) } func RplJoin(client *Client, channel *Channel) Reply { - return NewStringReply(client, "JOIN", channel.name) + return NewStringReply(client, JOIN, channel.name) } func RplPart(client *Client, channel *Channel, message string) Reply { - return NewStringReply(client, "PART", "%s :%s", channel, message) + return NewStringReply(client, PART, "%s :%s", channel, message) } func RplMode(client *Client, changes ModeChanges) Reply { - return NewStringReply(client, "MODE", "%s :%s", client.Nick(), changes) + return NewStringReply(client, MODE, "%s :%s", client.Nick(), changes) } func RplChannelMode(client *Client, channel *Channel, changes ChannelModeChanges) Reply { - return NewStringReply(client, "MODE", "%s %s", channel, changes) + return NewStringReply(client, MODE, "%s %s", channel, changes) } func RplTopicMsg(source Identifier, channel *Channel) Reply { - return NewStringReply(source, "TOPIC", "%s :%s", channel, channel.topic) + return NewStringReply(source, TOPIC, "%s :%s", channel, channel.topic) } func RplPing(server *Server, target Identifier) Reply { - return NewStringReply(server, "PING", target.Nick()) + return NewStringReply(server, PING, target.Nick()) } func RplPong(server *Server, client *Client) Reply { - return NewStringReply(server, "PONG", client.Nick()) + return NewStringReply(server, PONG, client.Nick()) } func RplQuit(client *Client, message string) Reply { - return NewStringReply(client, "QUIT", ":%s", message) + return NewStringReply(client, QUIT, ":%s", message) } func RplError(server *Server, target Identifier) Reply { - return NewStringReply(server, "ERROR", target.Nick()) + return NewStringReply(server, ERROR, target.Nick()) } func RplInviteMsg(channel *Channel, inviter *Client) Reply { - return NewStringReply(inviter, "INVITE", channel.name) + return NewStringReply(inviter, INVITE, channel.name) } // numeric replies diff --git a/irc/server.go b/irc/server.go index 10a5cb32..5a12fda4 100644 --- a/irc/server.go +++ b/irc/server.go @@ -9,20 +9,18 @@ import ( "log" "net" "os" - "sync" "time" ) type Server struct { channels ChannelNameMap + clients ClientNameMap commands chan Command ctime time.Time motdFile string - mutex *sync.Mutex name string operators map[string]string password string - clients ClientNameMap } func NewServer(config *Config) *Server { @@ -32,7 +30,6 @@ func NewServer(config *Config) *Server { commands: make(chan Command), ctime: time.Now(), motdFile: config.MOTD, - mutex: &sync.Mutex{}, name: config.Name, operators: make(map[string]string), password: config.Password, @@ -43,7 +40,6 @@ func NewServer(config *Config) *Server { } go server.receiveCommands() - for _, listenerConf := range config.Listeners { go server.listen(listenerConf) } @@ -51,12 +47,6 @@ func NewServer(config *Config) *Server { return server } -func (server *Server) withMutex(f func()) { - server.mutex.Lock() - defer server.mutex.Unlock() - f() -} - func (server *Server) receiveCommands() { for command := range server.commands { if DEBUG_SERVER { @@ -145,9 +135,7 @@ func (s *Server) GetOrMakeChannel(name string) *Channel { if !ok { channel = NewChannel(s, name) - s.withMutex(func() { - s.channels[name] = channel - }) + s.channels[name] = channel } return channel @@ -316,17 +304,9 @@ func (m *NickCommand) HandleServer(s *Server) { return } - // Make reply before changing nick. - reply := RplNick(c, m.nickname) - s.clients.Remove(c) - c.nick = m.nickname + c.commands <- m s.clients.Add(c) - - iclients := c.InterestedClients() - for iclient := range iclients { - iclient.Reply(reply) - } } func (m *UserMsgCommand) HandleServer(s *Server) { @@ -335,24 +315,10 @@ func (m *UserMsgCommand) HandleServer(s *Server) { func (m *QuitCommand) HandleServer(server *Server) { client := m.Client() - iclients := client.InterestedClients() - iclients.Remove(client) - for channel := range client.channels { - channel.withMutex(func() { - channel.members.Remove(client) - }) - } + server.clients.Remove(client) - client.Reply(RplError(server, client)) - client.Destroy() - - if len(iclients) > 0 { - reply := RplQuit(client, m.message) - for iclient := range iclients { - iclient.Reply(reply) - } - } + client.commands <- m } func (m *JoinCommand) HandleServer(s *Server) { @@ -374,9 +340,7 @@ func (m *JoinCommand) HandleServer(s *Server) { func (m *PartCommand) HandleServer(server *Server) { for _, chname := range m.channels { - server.mutex.Lock() channel := server.channels[chname] - server.mutex.Unlock() if channel == nil { m.Client().Reply(ErrNoSuchChannel(server, chname)) @@ -567,3 +531,11 @@ func (msg *NoticeCommand) HandleServer(server *Server) { } target.Reply(RplPrivMsg(msg.Client(), target, msg.message)) } + +func (msg *DestroyChannel) HandleServer(server *Server) { + server.channels.Remove(msg.channel) +} + +func (msg *DestroyClient) HandleServer(server *Server) { + server.clients.Remove(msg.client) +} diff --git a/irc/types.go b/irc/types.go index 554f5093..9ce6b236 100644 --- a/irc/types.go +++ b/irc/types.go @@ -28,9 +28,19 @@ func (mode UserMode) String() string { type Phase uint -type Numeric uint +type ReplyCode interface { + String() string +} -func (code Numeric) String() string { +type StringCode string + +func (code StringCode) String() string { + return string(code) +} + +type NumericCode uint + +func (code NumericCode) String() string { return fmt.Sprintf("%03d", code) } @@ -87,29 +97,43 @@ func (clients ClientNameMap) Remove(client *Client) error { type ChannelModeSet map[ChannelMode]bool -type ClientSet map[*Client]ChannelModeSet +type ClientSet map[*Client]bool func (clients ClientSet) Add(client *Client) { - clients[client] = make(ChannelModeSet) + clients[client] = true } func (clients ClientSet) Remove(client *Client) { delete(clients, client) } -func (clients ClientSet) HasMode(client *Client, mode ChannelMode) bool { - modes, ok := clients[client] +func (clients ClientSet) Has(client *Client) bool { + return clients[client] +} + +type MemberSet map[*Client]ChannelModeSet + +func (members MemberSet) Add(member *Client) { + members[member] = make(ChannelModeSet) +} + +func (members MemberSet) Remove(member *Client) { + delete(members, member) +} + +func (members MemberSet) Has(member *Client) bool { + _, ok := members[member] + return ok +} + +func (members MemberSet) HasMode(member *Client, mode ChannelMode) bool { + modes, ok := members[member] if !ok { return false } return modes[mode] } -func (clients ClientSet) Has(client *Client) bool { - _, ok := clients[client] - return ok -} - type ChannelSet map[*Channel]bool func (channels ChannelSet) Add(channel *Channel) { @@ -141,6 +165,7 @@ type Replier interface { } type Reply interface { + Code() ReplyCode Format(*Client) []string Source() Identifier } @@ -172,6 +197,10 @@ type ChannelCommand interface { HandleChannel(channel *Channel) } +type ClientCommand interface { + HandleClient(client *Client) +} + // // structs //