diff --git a/irc/client.go b/irc/client.go index b1c7fe6c..a5775af1 100644 --- a/irc/client.go +++ b/irc/client.go @@ -275,6 +275,7 @@ func (server *Server) RunClient(conn clientConn) { client.rawHostname = session.rawHostname client.proxiedIP = session.proxiedIP + server.stats.Add() client.run(session) } @@ -359,6 +360,15 @@ func (client *Client) IPString() string { return ip } +// t returns the translated version of the given string, based on the languages configured by the client. +func (client *Client) t(originalString string) string { + languageManager := client.server.Config().languageManager + if !languageManager.Enabled() { + return originalString + } + return languageManager.Translate(client.Languages(), originalString) +} + // // command goroutine // @@ -921,21 +931,6 @@ func (client *Client) LoggedIntoAccount() bool { return client.Account() != "" } -// RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses. -func (client *Client) RplISupport(rb *ResponseBuffer) { - translatedISupport := client.t("are supported by this server") - nick := client.Nick() - config := client.server.Config() - for _, cachedTokenLine := range config.Server.isupport.CachedReply { - length := len(cachedTokenLine) + 2 - tokenline := make([]string, length) - tokenline[0] = nick - copy(tokenline[1:], cachedTokenLine) - tokenline[length-1] = translatedISupport - rb.Add(nil, client.server.name, RPL_ISUPPORT, tokenline...) - } -} - // Quit sets the given quit message for the client. // (You must ensure separately that destroy() is called, e.g., by returning `true` from // the command handler or calling it yourself.) @@ -1094,16 +1089,8 @@ func (client *Client) destroy(session *Session) { client.server.accounts.Logout(client) - // send quit messages to friends - if registered { - client.server.stats.ChangeTotal(-1) - } - if client.HasMode(modes.Invisible) { - client.server.stats.ChangeInvisible(-1) - } - if client.HasMode(modes.Operator) || client.HasMode(modes.LocalOperator) { - client.server.stats.ChangeOperators(-1) - } + client.server.stats.Remove(registered, client.HasMode(modes.Invisible), + client.HasMode(modes.Operator) || client.HasMode(modes.LocalOperator)) // this happens under failure to return from BRB if quitMessage == "" { diff --git a/irc/handlers.go b/irc/handlers.go index 9414f4dc..942d3e99 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -1638,14 +1638,7 @@ func listHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp // LUSERS [ []] func lusersHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { - //TODO(vegax87) Fix network statistics and additional parameters - totalCount, invisibleCount, operCount := server.stats.GetStats() - - rb.Add(nil, server.name, RPL_LUSERCLIENT, client.nick, fmt.Sprintf(client.t("There are %[1]d users and %[2]d invisible on %[3]d server(s)"), totalCount-invisibleCount, invisibleCount, 1)) - rb.Add(nil, server.name, RPL_LUSEROP, client.nick, strconv.Itoa(operCount), client.t("IRC Operators online")) - rb.Add(nil, server.name, RPL_LUSERCHANNELS, client.nick, strconv.Itoa(server.channels.Len()), client.t("channels formed")) - rb.Add(nil, server.name, RPL_LUSERME, client.nick, fmt.Sprintf(client.t("I have %[1]d clients and %[2]d servers"), totalCount, 1)) - + server.Lusers(client, rb) return false } @@ -2582,7 +2575,7 @@ func userhostHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb * // VERSION func versionHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *ResponseBuffer) bool { rb.Add(nil, server.name, RPL_VERSION, client.nick, Ver, server.name) - client.RplISupport(rb) + server.RplISupport(client, rb) return false } diff --git a/irc/languages/languages.go b/irc/languages/languages.go index f0f76567..ee7f2591 100644 --- a/irc/languages/languages.go +++ b/irc/languages/languages.go @@ -164,6 +164,11 @@ func (lm *Manager) Count() int { return len(lm.Languages) } +// Enabled returns whether translation is enabled. +func (lm *Manager) Enabled() bool { + return len(lm.translations) != 0 +} + // Translators returns the languages we have and the translators. func (lm *Manager) Translators() []string { var tlist sort.StringSlice diff --git a/irc/numerics.go b/irc/numerics.go index f2f076d3..90218bfb 100644 --- a/irc/numerics.go +++ b/irc/numerics.go @@ -50,6 +50,8 @@ const ( RPL_TRACELOG = "261" RPL_TRACEEND = "262" RPL_TRYAGAIN = "263" + RPL_LOCALUSERS = "265" + RPL_GLOBALUSERS = "266" RPL_WHOISCERTFP = "276" RPL_AWAY = "301" RPL_USERHOST = "302" diff --git a/irc/server.go b/irc/server.go index 436b607e..12962e27 100644 --- a/irc/server.go +++ b/irc/server.go @@ -386,7 +386,7 @@ func (server *Server) tryRegister(c *Client, session *Session) (exiting bool) { c.SetRegistered() // count new user in statistics - server.stats.ChangeTotal(1) + server.stats.Register() server.monitorManager.AlertAbout(c, true) server.playRegistrationBurst(session) @@ -410,7 +410,8 @@ func (server *Server) playRegistrationBurst(session *Session) { session.Send(nil, server.name, RPL_MYINFO, d.nick, server.name, Ver, supportedUserModesString, supportedChannelModesString) rb := NewResponseBuffer(session) - c.RplISupport(rb) + server.RplISupport(c, rb) + server.Lusers(c, rb) server.MOTD(c, rb) rb.Send(true) @@ -423,11 +424,34 @@ func (server *Server) playRegistrationBurst(session *Session) { } } -// t returns the translated version of the given string, based on the languages configured by the client. -func (client *Client) t(originalString string) string { - // TODO(slingamn) investigate a fast path for this, using an atomic load to see if translation is disabled - languages := client.Languages() - return client.server.Languages().Translate(languages, originalString) +// RplISupport outputs our ISUPPORT lines to the client. This is used on connection and in VERSION responses. +func (server *Server) RplISupport(client *Client, rb *ResponseBuffer) { + translatedISupport := client.t("are supported by this server") + nick := client.Nick() + config := server.Config() + for _, cachedTokenLine := range config.Server.isupport.CachedReply { + length := len(cachedTokenLine) + 2 + tokenline := make([]string, length) + tokenline[0] = nick + copy(tokenline[1:], cachedTokenLine) + tokenline[length-1] = translatedISupport + rb.Add(nil, server.name, RPL_ISUPPORT, tokenline...) + } +} + +func (server *Server) Lusers(client *Client, rb *ResponseBuffer) { + nick := client.Nick() + stats := server.stats.GetValues() + + rb.Add(nil, server.name, RPL_LUSERCLIENT, nick, fmt.Sprintf(client.t("There are %[1]d users and %[2]d invisible on %[3]d server(s)"), stats.Total-stats.Invisible, stats.Invisible, 1)) + rb.Add(nil, server.name, RPL_LUSEROP, nick, strconv.Itoa(stats.Operators), client.t("IRC Operators online")) + rb.Add(nil, server.name, RPL_LUSERUNKNOWN, nick, strconv.Itoa(stats.Unknown), client.t("unregistered connections")) + rb.Add(nil, server.name, RPL_LUSERCHANNELS, nick, strconv.Itoa(server.channels.Len()), client.t("channels formed")) + rb.Add(nil, server.name, RPL_LUSERME, nick, fmt.Sprintf(client.t("I have %[1]d clients and %[2]d servers"), stats.Total, 1)) + total := strconv.Itoa(stats.Total) + max := strconv.Itoa(stats.Max) + rb.Add(nil, server.name, RPL_LOCALUSERS, nick, total, max, fmt.Sprintf(client.t("Current local users %[1]s, max %[2]s"), total, max)) + rb.Add(nil, server.name, RPL_GLOBALUSERS, nick, total, max, fmt.Sprintf(client.t("Current global users %[1]s, max %[2]s"), total, max)) } // MOTD serves the Message of the Day. diff --git a/irc/stats.go b/irc/stats.go index 0921f41a..5643ad87 100644 --- a/irc/stats.go +++ b/irc/stats.go @@ -4,43 +4,74 @@ import ( "sync" ) -// Stats contains the numbers of total, invisible and operators on the server -type Stats struct { - sync.RWMutex - - Total int +type StatsValues struct { + Unknown int // unregistered clients + Total int // registered clients, including invisible + Max int // high-water mark of registered clients Invisible int Operators int } -// ChangeTotal increments the total user count on server -func (s *Stats) ChangeTotal(i int) { - s.Lock() - defer s.Unlock() +// Stats tracks statistics for a running server +type Stats struct { + StatsValues - s.Total += i + mutex sync.Mutex } -// ChangeInvisible increments the invisible count -func (s *Stats) ChangeInvisible(i int) { - s.Lock() - defer s.Unlock() - - s.Invisible += i +// Adds an unregistered client +func (s *Stats) Add() { + s.mutex.Lock() + s.Unknown += 1 + s.mutex.Unlock() } -// ChangeOperators increases the operator count -func (s *Stats) ChangeOperators(i int) { - s.Lock() - defer s.Unlock() +// Transition a client from unregistered to registered +func (s *Stats) Register() { + s.mutex.Lock() + s.Unknown -= 1 + s.Total += 1 + if s.Max < s.Total { + s.Max = s.Total + } + s.mutex.Unlock() +} - s.Operators += i +// Modify the Invisible count +func (s *Stats) ChangeInvisible(increment int) { + s.mutex.Lock() + s.Invisible += increment + s.mutex.Unlock() +} + +// Modify the Operator count +func (s *Stats) ChangeOperators(increment int) { + s.mutex.Lock() + s.Operators += increment + s.mutex.Unlock() +} + +// Remove a user from the server +func (s *Stats) Remove(registered, invisible, operator bool) { + s.mutex.Lock() + if registered { + s.Total -= 1 + } else { + s.Unknown -= 1 + } + if invisible { + s.Invisible -= 1 + } + if operator { + s.Operators -= 1 + } + s.mutex.Unlock() } // GetStats retrives total, invisible and oper count -func (s *Stats) GetStats() (int, int, int) { - s.Lock() - defer s.Unlock() - - return s.Total, s.Invisible, s.Operators +func (s *Stats) GetValues() (result StatsValues) { + s.mutex.Lock() + result = s.StatsValues + s.mutex.Unlock() + return }