diff --git a/irc/client.go b/irc/client.go index 7047f164..07d4bc87 100644 --- a/irc/client.go +++ b/irc/client.go @@ -225,6 +225,7 @@ func (client *Client) ChangeNickname(nickname string) { // Make reply before changing nick to capture original source id. reply := RplNick(client, nickname) client.server.clients.Remove(client) + client.server.whoWas.Append(client) client.nick = nickname client.server.clients.Add(client) for friend := range client.Friends() { @@ -242,8 +243,8 @@ func (client *Client) Quit(message string) { } client.Reply(RplError("connection closed")) - client.hasQuit = true + client.server.whoWas.Append(client) friends := client.Friends() friends.Remove(client) client.destroy() diff --git a/irc/reply.go b/irc/reply.go index a32a5765..5f538df5 100644 --- a/irc/reply.go +++ b/irc/reply.go @@ -394,9 +394,10 @@ func (target *Client) RplTime() { "%s :%s", target.server.name, time.Now().Format(time.RFC1123)) } -func (target *Client) RplWhoWasUser(nickname, username, hostname, realname string) { +func (target *Client) RplWhoWasUser(whoWas *WhoWas) { target.NumericReply(RPL_WHOWASUSER, - "%s %s %s * :%s", nickname, username, hostname, realname) + "%s %s %s * :%s", + whoWas.nickname, whoWas.username, whoWas.hostname, whoWas.realname) } func (target *Client) RplEndOfWhoWas(nickname string) { diff --git a/irc/server.go b/irc/server.go index 16ee7b11..53e476fd 100644 --- a/irc/server.go +++ b/irc/server.go @@ -30,6 +30,7 @@ type Server struct { password []byte signals chan os.Signal timeout chan *Client + whoWas *WhoWasList } func NewServer(config *Config) *Server { @@ -46,6 +47,7 @@ func NewServer(config *Config) *Server { operators: config.Operators(), signals: make(chan os.Signal, 1), timeout: make(chan *Client, 16), + whoWas: NewWhoWasList(100), } if config.Server.Password != "" { @@ -846,8 +848,14 @@ func (msg *KillCommand) HandleServer(server *Server) { func (msg *WhoWasCommand) HandleServer(server *Server) { client := msg.Client() for _, nickname := range msg.nicknames { - // TODO implement nick history - client.ErrWasNoSuchNick(nickname) + results := server.whoWas.Find(nickname, msg.count) + if len(results) == 0 { + client.ErrWasNoSuchNick(nickname) + } else { + for _, whoWas := range results { + client.RplWhoWasUser(whoWas) + } + } client.RplEndOfWhoWas(nickname) } } diff --git a/irc/whowas.go b/irc/whowas.go new file mode 100644 index 00000000..008ed7f3 --- /dev/null +++ b/irc/whowas.go @@ -0,0 +1,73 @@ +package irc + +type WhoWasList struct { + buffer []*WhoWas + start uint + end uint +} + +type WhoWas struct { + nickname string + username string + hostname string + realname string +} + +func NewWhoWasList(size uint) *WhoWasList { + return &WhoWasList{ + buffer: make([]*WhoWas, size), + } +} + +func (list *WhoWasList) Append(client *Client) { + list.buffer[list.end] = &WhoWas{ + nickname: client.Nick(), + username: client.username, + hostname: client.hostname, + realname: client.realname, + } + list.end = (list.end + 1) % uint(len(list.buffer)) + if list.end == list.start { + list.start = (list.end + 1) % uint(len(list.buffer)) + } +} + +func (list *WhoWasList) Find(nickname string, limit int64) []*WhoWas { + results := make([]*WhoWas, 0) + for whoWas := range list.Each() { + if nickname != whoWas.nickname { + continue + } + results = append(results, whoWas) + if int64(len(results)) >= limit { + break + } + } + return results +} + +func (list *WhoWasList) prev(index uint) uint { + index -= 1 + if index < 0 { + index += uint(len(list.buffer)) + } + return index +} + +// Iterate the buffer in reverse. +func (list *WhoWasList) Each() <-chan *WhoWas { + ch := make(chan *WhoWas) + go func() { + defer close(ch) + if list.start == list.end { + return + } + start := list.prev(list.end) + end := list.prev(list.start) + for start != end { + ch <- list.buffer[start] + start = list.prev(start) + } + }() + return ch +}