mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-10 22:19:31 +01:00
get rid of mutexes in favor of channel-base syncing
This commit is contained in:
parent
74b8221db7
commit
e411dafda7
139
irc/channel.go
139
irc/channel.go
@ -2,17 +2,14 @@ package irc
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
banList []UserMask
|
||||
commands chan<- ChannelCommand
|
||||
destroyed bool
|
||||
flags map[ChannelMode]bool
|
||||
flags ChannelModeSet
|
||||
key string
|
||||
members ClientSet
|
||||
mutex *sync.Mutex
|
||||
members MemberSet
|
||||
name string
|
||||
replies chan<- Reply
|
||||
server *Server
|
||||
@ -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
|
||||
}
|
||||
|
||||
func (channel *Channel) Destroy() {
|
||||
if channel.IsDestroyed() {
|
||||
return
|
||||
type DestroyChannel struct {
|
||||
BaseCommand
|
||||
channel *Channel
|
||||
}
|
||||
|
||||
channel.withMutex(func() {
|
||||
channel.destroyed = true
|
||||
channel.members = make(ClientSet)
|
||||
})
|
||||
|
||||
channel.server.withMutex(func() {
|
||||
channel.server.channels.Remove(channel)
|
||||
func (channel *Channel) Destroy() {
|
||||
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)) {
|
||||
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)
|
||||
}
|
||||
|
135
irc/client.go
135
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
|
||||
}
|
||||
|
||||
// <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()
|
||||
}
|
||||
|
286
irc/constants.go
286
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 = '='
|
||||
|
59
irc/reply.go
59
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
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
51
irc/types.go
51
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
|
||||
//
|
||||
|
Loading…
Reference in New Issue
Block a user