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 (
"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)
}

View File

@ -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
}
// <mode>
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()
}

View File

@ -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 = '='

View File

@ -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

View File

@ -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)
}

View File

@ -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
//