3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-01 16:39:26 +01:00

get rid of mutexes in favor of channel-base syncing

This commit is contained in:
Jeremy Latt 2014-02-16 17:23:47 -08:00
parent 74b8221db7
commit e411dafda7
6 changed files with 397 additions and 351 deletions

View File

@ -2,21 +2,18 @@ package irc
import ( import (
"log" "log"
"sync"
) )
type Channel struct { type Channel struct {
banList []UserMask banList []UserMask
commands chan<- ChannelCommand commands chan<- ChannelCommand
destroyed bool flags ChannelModeSet
flags map[ChannelMode]bool key string
key string members MemberSet
members ClientSet name string
mutex *sync.Mutex replies chan<- Reply
name string server *Server
replies chan<- Reply topic string
server *Server
topic string
} }
func IsChannel(target string) bool { func IsChannel(target string) bool {
@ -38,9 +35,8 @@ func NewChannel(s *Server, name string) *Channel {
channel := &Channel{ channel := &Channel{
banList: make([]UserMask, 0), banList: make([]UserMask, 0),
commands: commands, commands: commands,
flags: make(map[ChannelMode]bool), flags: make(ChannelModeSet),
members: make(ClientSet), members: make(MemberSet),
mutex: &sync.Mutex{},
name: name, name: name,
replies: replies, replies: replies,
server: s, server: s,
@ -50,19 +46,17 @@ func NewChannel(s *Server, name string) *Channel {
return channel return channel
} }
type DestroyChannel struct {
BaseCommand
channel *Channel
}
func (channel *Channel) Destroy() { func (channel *Channel) Destroy() {
if channel.IsDestroyed() { channel.server.Command(&DestroyChannel{
return channel: channel,
}
channel.withMutex(func() {
channel.destroyed = true
channel.members = make(ClientSet)
})
channel.server.withMutex(func() {
channel.server.channels.Remove(channel)
}) })
close(channel.commands)
close(channel.replies)
} }
func (channel *Channel) Command(command ChannelCommand) { func (channel *Channel) Command(command ChannelCommand) {
@ -75,13 +69,6 @@ func (channel *Channel) Reply(reply Reply) {
func (channel *Channel) receiveCommands(commands <-chan ChannelCommand) { func (channel *Channel) receiveCommands(commands <-chan ChannelCommand) {
for command := range commands { for command := range commands {
if channel.IsDestroyed() {
if DEBUG_CHANNEL {
log.Printf("%s → %s %s dropped", command.Source(), channel, command)
}
continue
}
if DEBUG_CHANNEL { if DEBUG_CHANNEL {
log.Printf("%s → %s %s", command.Source(), channel, command) 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) { func (channel *Channel) receiveReplies(replies <-chan Reply) {
for reply := range replies { for reply := range replies {
if channel.IsDestroyed() {
if DEBUG_CHANNEL {
log.Printf("%s ← %s %s dropped", channel, reply.Source(), reply)
}
continue
}
if DEBUG_CHANNEL { if DEBUG_CHANNEL {
log.Printf("%s ← %s %s", channel, reply.Source(), reply) log.Printf("%s ← %s %s", channel, reply.Source(), reply)
} }
channel.withMutex(func() {
for client := range channel.members { for client := range channel.members {
if IsPrivMsg(reply) && (reply.Source() == Identifier(client)) { if (reply.Code() == ReplyCode(PRIVMSG)) &&
continue (reply.Source() == Identifier(client)) {
} continue
client.Reply(reply)
} }
}) client.Reply(reply)
}
} }
} }
@ -198,25 +164,9 @@ func (channel *Channel) ModeString() (str string) {
return return
} }
func (channel *Channel) withMutex(f func()) { type JoinChannel struct {
channel.mutex.Lock() BaseCommand
defer channel.mutex.Unlock() channel *Channel
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)
} }
// //
@ -230,7 +180,33 @@ func (m *JoinCommand) HandleChannel(channel *Channel) {
return 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) { func (m *PartCommand) HandleChannel(channel *Channel) {
@ -244,9 +220,15 @@ func (m *PartCommand) HandleChannel(channel *Channel) {
channel.Reply(RplPart(client, channel, m.Message())) channel.Reply(RplPart(client, channel, m.Message()))
channel.members.Remove(client) 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() { if channel.IsEmpty() {
channel.Destroy() channel.Destroy()
} }
@ -387,3 +369,18 @@ func (m *NoticeCommand) HandleChannel(channel *Channel) {
} }
channel.Reply(RplNotice(client, channel, m.message)) 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)
}

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"log" "log"
"net" "net"
"sync"
"time" "time"
) )
@ -13,13 +12,13 @@ type Client struct {
away bool away bool
awayMessage string awayMessage string
channels ChannelSet channels ChannelSet
commands chan ClientCommand
ctime time.Time ctime time.Time
destroyed bool friends map[*Client]uint
hostname string hostname string
idleTimer *time.Timer idleTimer *time.Timer
invisible bool invisible bool
loginTimer *time.Timer loginTimer *time.Timer
mutex *sync.Mutex
nick string nick string
operator bool operator bool
phase Phase phase Phase
@ -36,16 +35,18 @@ func NewClient(server *Server, conn net.Conn) *Client {
client := &Client{ client := &Client{
atime: now, atime: now,
channels: make(ChannelSet), channels: make(ChannelSet),
commands: make(chan ClientCommand),
ctime: now, ctime: now,
friends: make(map[*Client]uint),
hostname: AddrLookupHostname(conn.RemoteAddr()), hostname: AddrLookupHostname(conn.RemoteAddr()),
phase: server.InitPhase(), phase: server.InitPhase(),
replies: make(chan Reply), replies: make(chan Reply),
server: server, server: server,
socket: NewSocket(conn), 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.readCommands()
go client.writeReplies() go client.writeReplies()
@ -53,10 +54,6 @@ func NewClient(server *Server, conn net.Conn) *Client {
} }
func (client *Client) Touch() { func (client *Client) Touch() {
if client.IsDestroyed() {
return
}
client.atime = time.Now() client.atime = time.Now()
if client.quitTimer != nil { if client.quitTimer != nil {
@ -89,10 +86,6 @@ func (client *Client) ConnectionTimeout() {
} }
func (client *Client) ConnectionClosed() { func (client *Client) ConnectionClosed() {
if client.IsDestroyed() {
return
}
msg := &QuitCommand{ msg := &QuitCommand{
message: "connection closed", message: "connection closed",
} }
@ -100,6 +93,12 @@ func (client *Client) ConnectionClosed() {
client.server.Command(msg) client.server.Command(msg)
} }
func (client *Client) readClientCommands() {
for command := range client.commands {
command.HandleClient(client)
}
}
func (c *Client) readCommands() { func (c *Client) readCommands() {
for line := range c.socket.Read() { for line := range c.socket.Read() {
m, err := ParseCommand(line) m, err := ParseCommand(line)
@ -126,13 +125,6 @@ func (c *Client) readCommands() {
func (client *Client) writeReplies() { func (client *Client) writeReplies() {
for reply := range client.replies { for reply := range client.replies {
if client.IsDestroyed() {
if DEBUG_CLIENT {
log.Printf("%s ← %s dropped", client, reply)
}
continue
}
if DEBUG_CLIENT { if DEBUG_CLIENT {
log.Printf("%s ← %s", client, reply) log.Printf("%s ← %s", client, reply)
} }
@ -143,24 +135,12 @@ func (client *Client) writeReplies() {
} }
} }
func (client *Client) IsDestroyed() bool { type DestroyClient struct {
client.mutex.Lock() BaseCommand
defer client.mutex.Unlock() client *Client
return client.destroyed
} }
func (client *Client) Destroy() { 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() client.socket.Close()
if client.idleTimer != nil { if client.idleTimer != nil {
@ -171,14 +151,15 @@ func (client *Client) Destroy() {
client.quitTimer.Stop() client.quitTimer.Stop()
} }
client.channels = make(ChannelSet) // clear channel list cmd := &DestroyClient{
client.server.clients.Remove(client) client: client,
client.mutex.Unlock()
if DEBUG_CLIENT {
log.Printf("%s destroyed", client)
} }
for channel := range client.channels {
channel.Command(cmd)
}
client.server.Command(cmd)
} }
func (client *Client) Reply(reply Reply) { func (client *Client) Reply(reply Reply) {
@ -199,16 +180,6 @@ func (client *Client) HasUsername() bool {
return client.username != "" 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
}
// <mode> // <mode>
func (c *Client) ModeString() (str string) { func (c *Client) ModeString() (str string) {
if c.invisible { if c.invisible {
@ -246,3 +217,63 @@ func (c *Client) Id() string {
func (c *Client) String() string { func (c *Client) String() string {
return c.UserHost() 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()
}

View File

@ -23,143 +23,157 @@ const (
IDLE_TIMEOUT = time.Minute // how long before a client is considered idle 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 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 // numeric codes
RPL_WELCOME Numeric = 1 RPL_WELCOME NumericCode = 1
RPL_YOURHOST Numeric = 2 RPL_YOURHOST NumericCode = 2
RPL_CREATED Numeric = 3 RPL_CREATED NumericCode = 3
RPL_MYINFO Numeric = 4 RPL_MYINFO NumericCode = 4
RPL_BOUNCE Numeric = 5 RPL_BOUNCE NumericCode = 5
RPL_TRACELINK Numeric = 200 RPL_TRACELINK NumericCode = 200
RPL_TRACECONNECTING Numeric = 201 RPL_TRACECONNECTING NumericCode = 201
RPL_TRACEHANDSHAKE Numeric = 202 RPL_TRACEHANDSHAKE NumericCode = 202
RPL_TRACEUNKNOWN Numeric = 203 RPL_TRACEUNKNOWN NumericCode = 203
RPL_TRACEOPERATOR Numeric = 204 RPL_TRACEOPERATOR NumericCode = 204
RPL_TRACEUSER Numeric = 205 RPL_TRACEUSER NumericCode = 205
RPL_TRACESERVER Numeric = 206 RPL_TRACESERVER NumericCode = 206
RPL_TRACESERVICE Numeric = 207 RPL_TRACESERVICE NumericCode = 207
RPL_TRACENEWTYPE Numeric = 208 RPL_TRACENEWTYPE NumericCode = 208
RPL_TRACECLASS Numeric = 209 RPL_TRACECLASS NumericCode = 209
RPL_TRACERECONNECT Numeric = 210 RPL_TRACERECONNECT NumericCode = 210
RPL_STATSLINKINFO Numeric = 211 RPL_STATSLINKINFO NumericCode = 211
RPL_STATSCOMMANDS Numeric = 212 RPL_STATSCOMMANDS NumericCode = 212
RPL_ENDOFSTATS Numeric = 219 RPL_ENDOFSTATS NumericCode = 219
RPL_UMODEIS Numeric = 221 RPL_UMODEIS NumericCode = 221
RPL_SERVLIST Numeric = 234 RPL_SERVLIST NumericCode = 234
RPL_SERVLISTEND Numeric = 235 RPL_SERVLISTEND NumericCode = 235
RPL_STATSUPTIME Numeric = 242 RPL_STATSUPTIME NumericCode = 242
RPL_STATSOLINE Numeric = 243 RPL_STATSOLINE NumericCode = 243
RPL_LUSERCLIENT Numeric = 251 RPL_LUSERCLIENT NumericCode = 251
RPL_LUSEROP Numeric = 252 RPL_LUSEROP NumericCode = 252
RPL_LUSERUNKNOWN Numeric = 253 RPL_LUSERUNKNOWN NumericCode = 253
RPL_LUSERCHANNELS Numeric = 254 RPL_LUSERCHANNELS NumericCode = 254
RPL_LUSERME Numeric = 255 RPL_LUSERME NumericCode = 255
RPL_ADMINME Numeric = 256 RPL_ADMINME NumericCode = 256
RPL_ADMINLOC1 Numeric = 257 RPL_ADMINLOC1 NumericCode = 257
RPL_ADMINLOC2 Numeric = 258 RPL_ADMINLOC2 NumericCode = 258
RPL_ADMINEMAIL Numeric = 259 RPL_ADMINEMAIL NumericCode = 259
RPL_TRACELOG Numeric = 261 RPL_TRACELOG NumericCode = 261
RPL_TRACEEND Numeric = 262 RPL_TRACEEND NumericCode = 262
RPL_TRYAGAIN Numeric = 263 RPL_TRYAGAIN NumericCode = 263
RPL_AWAY Numeric = 301 RPL_AWAY NumericCode = 301
RPL_USERHOST Numeric = 302 RPL_USERHOST NumericCode = 302
RPL_ISON Numeric = 303 RPL_ISON NumericCode = 303
RPL_UNAWAY Numeric = 305 RPL_UNAWAY NumericCode = 305
RPL_NOWAWAY Numeric = 306 RPL_NOWAWAY NumericCode = 306
RPL_WHOISUSER Numeric = 311 RPL_WHOISUSER NumericCode = 311
RPL_WHOISSERVER Numeric = 312 RPL_WHOISSERVER NumericCode = 312
RPL_WHOISOPERATOR Numeric = 313 RPL_WHOISOPERATOR NumericCode = 313
RPL_WHOWASUSER Numeric = 314 RPL_WHOWASUSER NumericCode = 314
RPL_ENDOFWHO Numeric = 315 RPL_ENDOFWHO NumericCode = 315
RPL_WHOISIDLE Numeric = 317 RPL_WHOISIDLE NumericCode = 317
RPL_ENDOFWHOIS Numeric = 318 RPL_ENDOFWHOIS NumericCode = 318
RPL_WHOISCHANNELS Numeric = 319 RPL_WHOISCHANNELS NumericCode = 319
RPL_LIST Numeric = 322 RPL_LIST NumericCode = 322
RPL_LISTEND Numeric = 323 RPL_LISTEND NumericCode = 323
RPL_CHANNELMODEIS Numeric = 324 RPL_CHANNELMODEIS NumericCode = 324
RPL_UNIQOPIS Numeric = 325 RPL_UNIQOPIS NumericCode = 325
RPL_NOTOPIC Numeric = 331 RPL_NOTOPIC NumericCode = 331
RPL_TOPIC Numeric = 332 RPL_TOPIC NumericCode = 332
RPL_INVITING Numeric = 341 RPL_INVITING NumericCode = 341
RPL_SUMMONING Numeric = 342 RPL_SUMMONING NumericCode = 342
RPL_INVITELIST Numeric = 346 RPL_INVITELIST NumericCode = 346
RPL_ENDOFINVITELIST Numeric = 347 RPL_ENDOFINVITELIST NumericCode = 347
RPL_EXCEPTLIST Numeric = 348 RPL_EXCEPTLIST NumericCode = 348
RPL_ENDOFEXCEPTLIST Numeric = 349 RPL_ENDOFEXCEPTLIST NumericCode = 349
RPL_VERSION Numeric = 351 RPL_VERSION NumericCode = 351
RPL_WHOREPLY Numeric = 352 RPL_WHOREPLY NumericCode = 352
RPL_NAMREPLY Numeric = 353 RPL_NAMREPLY NumericCode = 353
RPL_LINKS Numeric = 364 RPL_LINKS NumericCode = 364
RPL_ENDOFLINKS Numeric = 365 RPL_ENDOFLINKS NumericCode = 365
RPL_ENDOFNAMES Numeric = 366 RPL_ENDOFNAMES NumericCode = 366
RPL_BANLIST Numeric = 367 RPL_BANLIST NumericCode = 367
RPL_ENDOFBANLIST Numeric = 368 RPL_ENDOFBANLIST NumericCode = 368
RPL_ENDOFWHOWAS Numeric = 369 RPL_ENDOFWHOWAS NumericCode = 369
RPL_INFO Numeric = 371 RPL_INFO NumericCode = 371
RPL_MOTD Numeric = 372 RPL_MOTD NumericCode = 372
RPL_ENDOFINFO Numeric = 374 RPL_ENDOFINFO NumericCode = 374
RPL_MOTDSTART Numeric = 375 RPL_MOTDSTART NumericCode = 375
RPL_ENDOFMOTD Numeric = 376 RPL_ENDOFMOTD NumericCode = 376
RPL_YOUREOPER Numeric = 381 RPL_YOUREOPER NumericCode = 381
RPL_REHASHING Numeric = 382 RPL_REHASHING NumericCode = 382
RPL_YOURESERVICE Numeric = 383 RPL_YOURESERVICE NumericCode = 383
RPL_TIME Numeric = 391 RPL_TIME NumericCode = 391
RPL_USERSSTART Numeric = 392 RPL_USERSSTART NumericCode = 392
RPL_USERS Numeric = 393 RPL_USERS NumericCode = 393
RPL_ENDOFUSERS Numeric = 394 RPL_ENDOFUSERS NumericCode = 394
RPL_NOUSERS Numeric = 395 RPL_NOUSERS NumericCode = 395
ERR_NOSUCHNICK Numeric = 401 ERR_NOSUCHNICK NumericCode = 401
ERR_NOSUCHSERVER Numeric = 402 ERR_NOSUCHSERVER NumericCode = 402
ERR_NOSUCHCHANNEL Numeric = 403 ERR_NOSUCHCHANNEL NumericCode = 403
ERR_CANNOTSENDTOCHAN Numeric = 404 ERR_CANNOTSENDTOCHAN NumericCode = 404
ERR_TOOMANYCHANNELS Numeric = 405 ERR_TOOMANYCHANNELS NumericCode = 405
ERR_WASNOSUCHNICK Numeric = 406 ERR_WASNOSUCHNICK NumericCode = 406
ERR_TOOMANYTARGETS Numeric = 407 ERR_TOOMANYTARGETS NumericCode = 407
ERR_NOSUCHSERVICE Numeric = 408 ERR_NOSUCHSERVICE NumericCode = 408
ERR_NOORIGIN Numeric = 409 ERR_NOORIGIN NumericCode = 409
ERR_NORECIPIENT Numeric = 411 ERR_NORECIPIENT NumericCode = 411
ERR_NOTEXTTOSEND Numeric = 412 ERR_NOTEXTTOSEND NumericCode = 412
ERR_NOTOPLEVEL Numeric = 413 ERR_NOTOPLEVEL NumericCode = 413
ERR_WILDTOPLEVEL Numeric = 414 ERR_WILDTOPLEVEL NumericCode = 414
ERR_BADMASK Numeric = 415 ERR_BADMASK NumericCode = 415
ERR_UNKNOWNCOMMAND Numeric = 421 ERR_UNKNOWNCOMMAND NumericCode = 421
ERR_NOMOTD Numeric = 422 ERR_NOMOTD NumericCode = 422
ERR_NOADMININFO Numeric = 423 ERR_NOADMININFO NumericCode = 423
ERR_FILEERROR Numeric = 424 ERR_FILEERROR NumericCode = 424
ERR_NONICKNAMEGIVEN Numeric = 431 ERR_NONICKNAMEGIVEN NumericCode = 431
ERR_ERRONEUSNICKNAME Numeric = 432 ERR_ERRONEUSNICKNAME NumericCode = 432
ERR_NICKNAMEINUSE Numeric = 433 ERR_NICKNAMEINUSE NumericCode = 433
ERR_NICKCOLLISION Numeric = 436 ERR_NICKCOLLISION NumericCode = 436
ERR_UNAVAILRESOURCE Numeric = 437 ERR_UNAVAILRESOURCE NumericCode = 437
ERR_USERNOTINCHANNEL Numeric = 441 ERR_USERNOTINCHANNEL NumericCode = 441
ERR_NOTONCHANNEL Numeric = 442 ERR_NOTONCHANNEL NumericCode = 442
ERR_USERONCHANNEL Numeric = 443 ERR_USERONCHANNEL NumericCode = 443
ERR_NOLOGIN Numeric = 444 ERR_NOLOGIN NumericCode = 444
ERR_SUMMONDISABLED Numeric = 445 ERR_SUMMONDISABLED NumericCode = 445
ERR_USERSDISABLED Numeric = 446 ERR_USERSDISABLED NumericCode = 446
ERR_NOTREGISTERED Numeric = 451 ERR_NOTREGISTERED NumericCode = 451
ERR_NEEDMOREPARAMS Numeric = 461 ERR_NEEDMOREPARAMS NumericCode = 461
ERR_ALREADYREGISTRED Numeric = 462 ERR_ALREADYREGISTRED NumericCode = 462
ERR_NOPERMFORHOST Numeric = 463 ERR_NOPERMFORHOST NumericCode = 463
ERR_PASSWDMISMATCH Numeric = 464 ERR_PASSWDMISMATCH NumericCode = 464
ERR_YOUREBANNEDCREEP Numeric = 465 ERR_YOUREBANNEDCREEP NumericCode = 465
ERR_YOUWILLBEBANNED Numeric = 466 ERR_YOUWILLBEBANNED NumericCode = 466
ERR_KEYSET Numeric = 467 ERR_KEYSET NumericCode = 467
ERR_CHANNELISFULL Numeric = 471 ERR_CHANNELISFULL NumericCode = 471
ERR_UNKNOWNMODE Numeric = 472 ERR_UNKNOWNMODE NumericCode = 472
ERR_INVITEONLYCHAN Numeric = 473 ERR_INVITEONLYCHAN NumericCode = 473
ERR_BANNEDFROMCHAN Numeric = 474 ERR_BANNEDFROMCHAN NumericCode = 474
ERR_BADCHANNELKEY Numeric = 475 ERR_BADCHANNELKEY NumericCode = 475
ERR_BADCHANMASK Numeric = 476 ERR_BADCHANMASK NumericCode = 476
ERR_NOCHANMODES Numeric = 477 ERR_NOCHANMODES NumericCode = 477
ERR_BANLISTFULL Numeric = 478 ERR_BANLISTFULL NumericCode = 478
ERR_NOPRIVILEGES Numeric = 481 ERR_NOPRIVILEGES NumericCode = 481
ERR_CHANOPRIVSNEEDED Numeric = 482 ERR_CHANOPRIVSNEEDED NumericCode = 482
ERR_CANTKILLSERVER Numeric = 483 ERR_CANTKILLSERVER NumericCode = 483
ERR_RESTRICTED Numeric = 484 ERR_RESTRICTED NumericCode = 484
ERR_UNIQOPPRIVSNEEDED Numeric = 485 ERR_UNIQOPPRIVSNEEDED NumericCode = 485
ERR_NOOPERHOST Numeric = 491 ERR_NOOPERHOST NumericCode = 491
ERR_UMODEUNKNOWNFLAG Numeric = 501 ERR_UMODEUNKNOWNFLAG NumericCode = 501
ERR_USERSDONTMATCH Numeric = 502 ERR_USERSDONTMATCH NumericCode = 502
Add ModeOp = '+' Add ModeOp = '+'
List ModeOp = '=' List ModeOp = '='

View File

@ -15,11 +15,16 @@ func joinedLen(names []string) int {
} }
type BaseReply struct { type BaseReply struct {
code ReplyCode
id string id string
message string message string
source Identifier source Identifier
} }
func (reply *BaseReply) Code() ReplyCode {
return reply.code
}
func (reply *BaseReply) SetSource(source Identifier) { func (reply *BaseReply) SetSource(source Identifier) {
reply.id = source.Id() reply.id = source.Id()
reply.source = source reply.source = source
@ -31,16 +36,14 @@ func (reply *BaseReply) Source() Identifier {
type StringReply struct { type StringReply struct {
BaseReply BaseReply
code string
} }
func NewStringReply(source Identifier, code string, func NewStringReply(source Identifier, code StringCode,
format string, args ...interface{}) *StringReply { format string, args ...interface{}) *StringReply {
reply := &StringReply{ reply := &StringReply{}
code: code, reply.code = code
}
reply.SetSource(source)
reply.message = fmt.Sprintf(format, args...) reply.message = fmt.Sprintf(format, args...)
reply.SetSource(source)
return reply return reply
} }
@ -57,16 +60,14 @@ func (reply *StringReply) String() string {
type NumericReply struct { type NumericReply struct {
BaseReply BaseReply
code Numeric
} }
func NewNumericReply(source Identifier, code Numeric, format string, func NewNumericReply(source Identifier, code NumericCode, format string,
args ...interface{}) *NumericReply { args ...interface{}) *NumericReply {
reply := &NumericReply{ reply := &NumericReply{}
code: code, reply.code = code
}
reply.SetSource(source)
reply.message = fmt.Sprintf(format, args...) reply.message = fmt.Sprintf(format, args...)
reply.SetSource(source)
return reply return reply
} }
@ -92,7 +93,7 @@ func NewNamesReply(channel *Channel) Reply {
reply := &NamesReply{ reply := &NamesReply{
channel: channel, channel: channel,
} }
reply.SetSource(channel) reply.SetSource(channel.server)
return reply return reply
} }
@ -107,14 +108,16 @@ func (reply *NamesReply) Format(client *Client) []string {
nicks := reply.channel.Nicks() nicks := reply.channel.Nicks()
for to < len(nicks) { for to < len(nicks) {
if (from < (to - 1)) && tooLong(nicks[from:to]) { 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 from, to = to-1, to
} else { } else {
to += 1 to += 1
} }
} }
if from < len(nicks) { 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)...) lines = append(lines, RplEndOfNames(reply.channel).Format(client)...)
return lines return lines
@ -128,56 +131,56 @@ func (reply *NamesReply) String() string {
// messaging replies // messaging replies
func RplPrivMsg(source Identifier, target Identifier, message string) Reply { 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 { 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 { func RplNick(source Identifier, newNick string) Reply {
return NewStringReply(source, "NICK", newNick) return NewStringReply(source, NICK, newNick)
} }
func RplJoin(client *Client, channel *Channel) Reply { 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 { 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 { 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, func RplChannelMode(client *Client, channel *Channel,
changes ChannelModeChanges) Reply { 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 { 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 { 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 { 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 { 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 { 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 { func RplInviteMsg(channel *Channel, inviter *Client) Reply {
return NewStringReply(inviter, "INVITE", channel.name) return NewStringReply(inviter, INVITE, channel.name)
} }
// numeric replies // numeric replies

View File

@ -9,20 +9,18 @@ import (
"log" "log"
"net" "net"
"os" "os"
"sync"
"time" "time"
) )
type Server struct { type Server struct {
channels ChannelNameMap channels ChannelNameMap
clients ClientNameMap
commands chan Command commands chan Command
ctime time.Time ctime time.Time
motdFile string motdFile string
mutex *sync.Mutex
name string name string
operators map[string]string operators map[string]string
password string password string
clients ClientNameMap
} }
func NewServer(config *Config) *Server { func NewServer(config *Config) *Server {
@ -32,7 +30,6 @@ func NewServer(config *Config) *Server {
commands: make(chan Command), commands: make(chan Command),
ctime: time.Now(), ctime: time.Now(),
motdFile: config.MOTD, motdFile: config.MOTD,
mutex: &sync.Mutex{},
name: config.Name, name: config.Name,
operators: make(map[string]string), operators: make(map[string]string),
password: config.Password, password: config.Password,
@ -43,7 +40,6 @@ func NewServer(config *Config) *Server {
} }
go server.receiveCommands() go server.receiveCommands()
for _, listenerConf := range config.Listeners { for _, listenerConf := range config.Listeners {
go server.listen(listenerConf) go server.listen(listenerConf)
} }
@ -51,12 +47,6 @@ func NewServer(config *Config) *Server {
return server return server
} }
func (server *Server) withMutex(f func()) {
server.mutex.Lock()
defer server.mutex.Unlock()
f()
}
func (server *Server) receiveCommands() { func (server *Server) receiveCommands() {
for command := range server.commands { for command := range server.commands {
if DEBUG_SERVER { if DEBUG_SERVER {
@ -145,9 +135,7 @@ func (s *Server) GetOrMakeChannel(name string) *Channel {
if !ok { if !ok {
channel = NewChannel(s, name) channel = NewChannel(s, name)
s.withMutex(func() { s.channels[name] = channel
s.channels[name] = channel
})
} }
return channel return channel
@ -316,17 +304,9 @@ func (m *NickCommand) HandleServer(s *Server) {
return return
} }
// Make reply before changing nick.
reply := RplNick(c, m.nickname)
s.clients.Remove(c) s.clients.Remove(c)
c.nick = m.nickname c.commands <- m
s.clients.Add(c) s.clients.Add(c)
iclients := c.InterestedClients()
for iclient := range iclients {
iclient.Reply(reply)
}
} }
func (m *UserMsgCommand) HandleServer(s *Server) { func (m *UserMsgCommand) HandleServer(s *Server) {
@ -335,24 +315,10 @@ func (m *UserMsgCommand) HandleServer(s *Server) {
func (m *QuitCommand) HandleServer(server *Server) { func (m *QuitCommand) HandleServer(server *Server) {
client := m.Client() client := m.Client()
iclients := client.InterestedClients()
iclients.Remove(client)
for channel := range client.channels { server.clients.Remove(client)
channel.withMutex(func() {
channel.members.Remove(client)
})
}
client.Reply(RplError(server, client)) client.commands <- m
client.Destroy()
if len(iclients) > 0 {
reply := RplQuit(client, m.message)
for iclient := range iclients {
iclient.Reply(reply)
}
}
} }
func (m *JoinCommand) HandleServer(s *Server) { func (m *JoinCommand) HandleServer(s *Server) {
@ -374,9 +340,7 @@ func (m *JoinCommand) HandleServer(s *Server) {
func (m *PartCommand) HandleServer(server *Server) { func (m *PartCommand) HandleServer(server *Server) {
for _, chname := range m.channels { for _, chname := range m.channels {
server.mutex.Lock()
channel := server.channels[chname] channel := server.channels[chname]
server.mutex.Unlock()
if channel == nil { if channel == nil {
m.Client().Reply(ErrNoSuchChannel(server, chname)) m.Client().Reply(ErrNoSuchChannel(server, chname))
@ -567,3 +531,11 @@ func (msg *NoticeCommand) HandleServer(server *Server) {
} }
target.Reply(RplPrivMsg(msg.Client(), target, msg.message)) 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)
}

View File

@ -28,9 +28,19 @@ func (mode UserMode) String() string {
type Phase uint 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) return fmt.Sprintf("%03d", code)
} }
@ -87,29 +97,43 @@ func (clients ClientNameMap) Remove(client *Client) error {
type ChannelModeSet map[ChannelMode]bool type ChannelModeSet map[ChannelMode]bool
type ClientSet map[*Client]ChannelModeSet type ClientSet map[*Client]bool
func (clients ClientSet) Add(client *Client) { func (clients ClientSet) Add(client *Client) {
clients[client] = make(ChannelModeSet) clients[client] = true
} }
func (clients ClientSet) Remove(client *Client) { func (clients ClientSet) Remove(client *Client) {
delete(clients, client) delete(clients, client)
} }
func (clients ClientSet) HasMode(client *Client, mode ChannelMode) bool { func (clients ClientSet) Has(client *Client) bool {
modes, ok := clients[client] 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 { if !ok {
return false return false
} }
return modes[mode] return modes[mode]
} }
func (clients ClientSet) Has(client *Client) bool {
_, ok := clients[client]
return ok
}
type ChannelSet map[*Channel]bool type ChannelSet map[*Channel]bool
func (channels ChannelSet) Add(channel *Channel) { func (channels ChannelSet) Add(channel *Channel) {
@ -141,6 +165,7 @@ type Replier interface {
} }
type Reply interface { type Reply interface {
Code() ReplyCode
Format(*Client) []string Format(*Client) []string
Source() Identifier Source() Identifier
} }
@ -172,6 +197,10 @@ type ChannelCommand interface {
HandleChannel(channel *Channel) HandleChannel(channel *Channel)
} }
type ClientCommand interface {
HandleClient(client *Client)
}
// //
// structs // structs
// //