diff --git a/irc/accounts.go b/irc/accounts.go index fabb3e87..8536b31a 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -35,6 +35,7 @@ const ( keyCertToAccount = "account.creds.certfp %s" keyAccountChannels = "account.channels %s" // channels registered to the account keyAccountJoinedChannels = "account.joinedto %s" // channels a persistent client has joined + keyAccountLastSignoff = "account.lastsignoff %s" keyVHostQueueAcctToId = "vhostQueue %s" vhostRequestIdx = "vhostQueue" @@ -103,7 +104,7 @@ func (am *AccountManager) createAlwaysOnClients(config *Config) { account, err := am.LoadAccount(accountName) if err == nil && account.Verified && persistenceEnabled(config.Accounts.Bouncer.AlwaysOn, account.Settings.AlwaysOn) { - am.server.AddAlwaysOnClient(account, am.loadChannels(accountName)) + am.server.AddAlwaysOnClient(account, am.loadChannels(accountName), am.loadLastSignoff(accountName)) } } } @@ -534,6 +535,36 @@ func (am *AccountManager) loadChannels(account string) (channels []string) { return } +func (am *AccountManager) saveLastSignoff(account string, lastSignoff time.Time) { + key := fmt.Sprintf(keyAccountLastSignoff, account) + var val string + if !lastSignoff.IsZero() { + val = strconv.FormatInt(lastSignoff.UnixNano(), 10) + } + am.server.store.Update(func(tx *buntdb.Tx) error { + if val != "" { + tx.Set(key, val, nil) + } else { + tx.Delete(key) + } + return nil + }) +} + +func (am *AccountManager) loadLastSignoff(account string) (lastSignoff time.Time) { + key := fmt.Sprintf(keyAccountLastSignoff, account) + var lsText string + am.server.store.View(func(tx *buntdb.Tx) error { + lsText, _ = tx.Get(key) + return nil + }) + lsNum, err := strconv.ParseInt(lsText, 10, 64) + if err != nil { + return time.Unix(0, lsNum) + } + return +} + func (am *AccountManager) addRemoveCertfp(account, certfp string, add bool, hasPrivs bool) (err error) { certfp, err = utils.NormalizeCertfp(certfp) if err != nil { @@ -1034,6 +1065,7 @@ func (am *AccountManager) Unregister(account string) error { vhostQueueKey := fmt.Sprintf(keyVHostQueueAcctToId, casefoldedAccount) channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount) joinedChannelsKey := fmt.Sprintf(keyAccountJoinedChannels, casefoldedAccount) + lastSignoffKey := fmt.Sprintf(keyAccountLastSignoff, casefoldedAccount) var clients []*Client @@ -1070,6 +1102,7 @@ func (am *AccountManager) Unregister(account string) error { channelsStr, _ = tx.Get(channelsKey) tx.Delete(channelsKey) tx.Delete(joinedChannelsKey) + tx.Delete(lastSignoffKey) _, err := tx.Delete(vhostQueueKey) am.decrementVHostQueueCount(casefoldedAccount, err) diff --git a/irc/client.go b/irc/client.go index 2d95dae7..49d9b2db 100644 --- a/irc/client.go +++ b/irc/client.go @@ -306,7 +306,7 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) { client.run(session, proxyLine) } -func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string) { +func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSignoff time.Time) { now := time.Now().UTC() config := server.Config() @@ -322,7 +322,8 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string) rawHostname: server.name, realIP: utils.IPv4LoopbackAddress, - alwaysOn: true, + alwaysOn: true, + lastSignoff: lastSignoff, } client.SetMode(modes.TLS, true) @@ -1187,10 +1188,17 @@ func (client *Client) destroy(session *Session) { } if alwaysOn && remainingSessions == 0 { client.lastSignoff = lastSignoff + client.dirtyBits |= IncludeLastSignoff + } else { + lastSignoff = time.Time{} } exitedSnomaskSent := client.exitedSnomaskSent client.stateMutex.Unlock() + if !lastSignoff.IsZero() { + client.wakeWriter() + } + // destroy all applicable sessions: var quitMessage string for _, session := range sessionsToDestroy { @@ -1573,6 +1581,7 @@ func (client *Client) historyStatus(config *Config) (persistent, ephemeral bool, // TODO add a dirty flag for lastSignoff const ( IncludeChannels uint = 1 << iota + IncludeLastSignoff ) func (client *Client) markDirty(dirtyBits uint) { @@ -1609,7 +1618,7 @@ func (client *Client) writeLoop() { func (client *Client) performWrite() { client.stateMutex.Lock() - // TODO actually read dirtyBits in the future + dirtyBits := client.dirtyBits client.dirtyBits = 0 account := client.account client.stateMutex.Unlock() @@ -1619,10 +1628,18 @@ func (client *Client) performWrite() { return } - channels := client.Channels() - channelNames := make([]string, len(channels)) - for i, channel := range channels { - channelNames[i] = channel.Name() + if (dirtyBits & IncludeChannels) != 0 { + channels := client.Channels() + channelNames := make([]string, len(channels)) + for i, channel := range channels { + channelNames[i] = channel.Name() + } + client.server.accounts.saveChannels(account, channelNames) + } + if (dirtyBits & IncludeLastSignoff) != 0 { + client.stateMutex.RLock() + lastSignoff := client.lastSignoff + client.stateMutex.RUnlock() + client.server.accounts.saveLastSignoff(account, lastSignoff) } - client.server.accounts.saveChannels(account, channelNames) } diff --git a/irc/getters.go b/irc/getters.go index ab030451..d5815698 100644 --- a/irc/getters.go +++ b/irc/getters.go @@ -94,6 +94,12 @@ func (client *Client) AllSessionData(currentSession *Session) (data []SessionDat } func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSignoff time.Time) { + defer func() { + if !lastSignoff.IsZero() { + client.wakeWriter() + } + }() + client.stateMutex.Lock() defer client.stateMutex.Unlock() @@ -111,6 +117,7 @@ func (client *Client) AddSession(session *Session) (success bool, numSessions in // on the server with no sessions: lastSignoff = client.lastSignoff client.lastSignoff = time.Time{} + client.dirtyBits |= IncludeLastSignoff } client.sessions = newSessions return true, len(client.sessions), lastSignoff