From 70b20750aa03af0c9266ef6df6394105ce25f0b1 Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 18 Mar 2021 02:53:18 -0400 Subject: [PATCH] fix #1531 AWAY status should be tracked per-session: 1. With auto-away enabled, away status is aggregated across sessions (if any session is not away, the client is not away, else use the away status that was set most recently) 2. With auto-away disabled, we get the legacy behavior where AWAY applies directly to the client --- docs/MANUAL.md | 4 ---- irc/client.go | 25 +++++++++----------- irc/getters.go | 62 ++++++++++++++++++++++++++++++++++++------------- irc/handlers.go | 4 ++-- 4 files changed, 59 insertions(+), 36 deletions(-) diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 83bfc86a..3a40d1cf 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -621,10 +621,6 @@ In this section, we give an overview of the modes Oragono supports. These are the modes which can be set on you when you're connected. -### +a - Away - -If this mode is set, you're marked as being away. This mode is set with the /AWAY command. - ### +i - Invisible If this mode is set, you're marked as 'invisible'. This means that your channels won't be shown when users `/WHOIS` you (except for IRC operators, they can see all the channels you're in). diff --git a/irc/client.go b/irc/client.go index 0d275dcd..77047f5a 100644 --- a/irc/client.go +++ b/irc/client.go @@ -78,8 +78,6 @@ type Client struct { accountName string // display name of the account: uncasefolded, '*' if not logged in accountRegDate time.Time accountSettings AccountSettings - away bool - autoAway bool awayMessage string brbTimer BrbTimer channels ChannelSet @@ -177,6 +175,9 @@ type Session struct { quitMessage string + awayMessage string + awayAt time.Time + capabilities caps.Set capState caps.State capVersion caps.Version @@ -486,8 +487,6 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToStatus m } if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) { - client.autoAway = true - client.away = true client.awayMessage = client.t("User is currently disconnected") } } @@ -675,7 +674,7 @@ func (client *Client) run(session *Session) { session.playResume() session.resumeDetails = nil client.brbTimer.Disable() - client.SetAway(false, "") // clear BRB message if any + session.SetAway("") // clear BRB message if any } else { client.playReattachMessages(session) } @@ -1458,15 +1457,13 @@ func (client *Client) destroy(session *Session) { client.dirtyBits |= IncludeLastSeen } - autoAway := false + becameAutoAway := false var awayMessage string - if alwaysOn && !client.away && remainingSessions == 0 && - persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) { - autoAway = true - client.autoAway = true - client.away = true - awayMessage = config.languageManager.Translate(client.languages, `User is currently disconnected`) - client.awayMessage = awayMessage + if alwaysOn && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) { + wasAway := client.awayMessage != "" + client.setAutoAwayNoMutex(config) + awayMessage = client.awayMessage + becameAutoAway = !wasAway && awayMessage != "" } if client.registrationTimer != nil { @@ -1523,7 +1520,7 @@ func (client *Client) destroy(session *Session) { client.server.stats.Remove(registered, invisible, operator) } - if autoAway { + if becameAutoAway { dispatchAwayNotify(client, true, awayMessage) } diff --git a/irc/getters.go b/irc/getters.go index 5c1a43f5..2580683d 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -107,6 +107,7 @@ func (client *Client) AllSessionData(currentSession *Session, hasPrivs bool) (da } func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time, back bool) { + config := client.server.Config() client.stateMutex.Lock() defer client.stateMutex.Unlock() @@ -126,11 +127,12 @@ func (client *Client) AddSession(session *Session) (success bool, numSessions in client.setLastSeen(time.Now().UTC(), session.deviceID) } client.sessions = newSessions - if client.autoAway { - back = true - client.autoAway = false - client.away = false + // TODO(#1551) there should be a cap to opt out of this behavior on a session + if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) { client.awayMessage = "" + if len(client.sessions) == 1 { + back = true + } } return true, len(client.sessions), lastSeen, back } @@ -196,20 +198,54 @@ func (client *Client) Hostname() string { func (client *Client) Away() (result bool, message string) { client.stateMutex.Lock() - result, message = client.away, client.awayMessage + message = client.awayMessage client.stateMutex.Unlock() + result = client.awayMessage != "" return } -func (client *Client) SetAway(away bool, awayMessage string) (changed bool) { +func (session *Session) SetAway(awayMessage string) { + client := session.client + config := client.server.Config() + client.stateMutex.Lock() - changed = away != client.away - client.away = away - client.awayMessage = awayMessage - client.stateMutex.Unlock() + defer client.stateMutex.Unlock() + + session.awayMessage = awayMessage + session.awayAt = time.Now().UTC() + + autoAway := client.registered && client.alwaysOn && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) + if autoAway { + client.setAutoAwayNoMutex(config) + } else { + client.awayMessage = awayMessage + } return } +func (client *Client) setAutoAwayNoMutex(config *Config) { + // aggregate the away statuses of the individual sessions: + var globalAwayState string + var awaySetAt time.Time + for _, cSession := range client.sessions { + if cSession.awayMessage == "" { + // a session is active, we are not auto-away + client.awayMessage = "" + return + } else if cSession.awayAt.After(awaySetAt) { + // choose the latest available away message from any session + globalAwayState = cSession.awayMessage + awaySetAt = cSession.awayAt + } + } + if awaySetAt.IsZero() { + // no sessions, enable auto-away + client.awayMessage = config.languageManager.Translate(client.languages, `User is currently disconnected`) + } else { + client.awayMessage = globalAwayState + } +} + func (client *Client) AlwaysOn() (alwaysOn bool) { client.stateMutex.RLock() alwaysOn = client.registered && client.alwaysOn @@ -269,12 +305,6 @@ func (client *Client) AwayMessage() (result string) { return } -func (client *Client) SetAwayMessage(message string) { - client.stateMutex.Lock() - client.awayMessage = message - client.stateMutex.Unlock() -} - func (client *Client) Account() string { client.stateMutex.RLock() defer client.stateMutex.RUnlock() diff --git a/irc/handlers.go b/irc/handlers.go index ac417a6d..6f06b790 100644 --- a/irc/handlers.go +++ b/irc/handlers.go @@ -352,7 +352,7 @@ func awayHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respons } } - client.SetAway(isAway, awayMessage) + rb.session.SetAway(awayMessage) if isAway { rb.Add(nil, server.name, RPL_NOWAWAY, client.nick, client.t("You have been marked as being away")) @@ -439,7 +439,7 @@ func brbHandler(server *Server, client *Client, msg ircmsg.Message, rb *Response if len(client.Sessions()) == 1 { // true BRB - client.SetAway(true, message) + rb.session.SetAway(message) } return true