|
@@ -47,7 +47,6 @@ type Client struct {
|
47
|
47
|
accountName string // display name of the account: uncasefolded, '*' if not logged in
|
48
|
48
|
accountRegDate time.Time
|
49
|
49
|
accountSettings AccountSettings
|
50
|
|
- atime time.Time
|
51
|
50
|
away bool
|
52
|
51
|
awayMessage string
|
53
|
52
|
brbTimer BrbTimer
|
|
@@ -60,7 +59,8 @@ type Client struct {
|
60
|
59
|
invitedTo map[string]bool
|
61
|
60
|
isSTSOnly bool
|
62
|
61
|
languages []string
|
63
|
|
- lastSignoff time.Time // for always-on clients, the time their last session quit
|
|
62
|
+ lastActive time.Time // last time they sent a command that wasn't PONG or similar
|
|
63
|
+ lastSeen time.Time // last time they sent any kind of command
|
64
|
64
|
loginThrottle connection_limits.GenericThrottle
|
65
|
65
|
nick string
|
66
|
66
|
nickCasefolded string
|
|
@@ -103,8 +103,8 @@ func (s *saslStatus) Clear() {
|
103
|
103
|
type Session struct {
|
104
|
104
|
client *Client
|
105
|
105
|
|
106
|
|
- ctime time.Time
|
107
|
|
- atime time.Time
|
|
106
|
+ ctime time.Time
|
|
107
|
+ lastActive time.Time
|
108
|
108
|
|
109
|
109
|
socket *Socket
|
110
|
110
|
realIP net.IP
|
|
@@ -130,10 +130,10 @@ type Session struct {
|
130
|
130
|
|
131
|
131
|
registrationMessages int
|
132
|
132
|
|
133
|
|
- resumeID string
|
134
|
|
- resumeDetails *ResumeDetails
|
135
|
|
- zncPlaybackTimes *zncPlaybackTimes
|
136
|
|
- lastSignoff time.Time
|
|
133
|
+ resumeID string
|
|
134
|
+ resumeDetails *ResumeDetails
|
|
135
|
+ zncPlaybackTimes *zncPlaybackTimes
|
|
136
|
+ autoreplayMissedSince time.Time
|
137
|
137
|
|
138
|
138
|
batch MultilineBatch
|
139
|
139
|
}
|
|
@@ -247,11 +247,12 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
|
247
|
247
|
// give them 1k of grace over the limit:
|
248
|
248
|
socket := NewSocket(conn.Conn, ircmsg.MaxlenTagsFromClient+512+1024, config.Server.MaxSendQBytes)
|
249
|
249
|
client := &Client{
|
250
|
|
- atime: now,
|
251
|
|
- channels: make(ChannelSet),
|
252
|
|
- ctime: now,
|
253
|
|
- isSTSOnly: conn.Config.STSOnly,
|
254
|
|
- languages: server.Languages().Default(),
|
|
250
|
+ lastSeen: now,
|
|
251
|
+ lastActive: now,
|
|
252
|
+ channels: make(ChannelSet),
|
|
253
|
+ ctime: now,
|
|
254
|
+ isSTSOnly: conn.Config.STSOnly,
|
|
255
|
+ languages: server.Languages().Default(),
|
255
|
256
|
loginThrottle: connection_limits.GenericThrottle{
|
256
|
257
|
Duration: config.Accounts.LoginThrottling.Duration,
|
257
|
258
|
Limit: config.Accounts.LoginThrottling.MaxAttempts,
|
|
@@ -270,7 +271,7 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
|
270
|
271
|
capVersion: caps.Cap301,
|
271
|
272
|
capState: caps.NoneState,
|
272
|
273
|
ctime: now,
|
273
|
|
- atime: now,
|
|
274
|
+ lastActive: now,
|
274
|
275
|
realIP: realIP,
|
275
|
276
|
isTor: conn.Config.Tor,
|
276
|
277
|
}
|
|
@@ -306,24 +307,27 @@ func (server *Server) RunClient(conn clientConn, proxyLine string) {
|
306
|
307
|
client.run(session, proxyLine)
|
307
|
308
|
}
|
308
|
309
|
|
309
|
|
-func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSignoff time.Time) {
|
|
310
|
+func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastActive time.Time) {
|
310
|
311
|
now := time.Now().UTC()
|
311
|
312
|
config := server.Config()
|
|
313
|
+ if lastActive.IsZero() {
|
|
314
|
+ lastActive = now
|
|
315
|
+ }
|
312
|
316
|
|
313
|
317
|
client := &Client{
|
314
|
|
- atime: now,
|
315
|
|
- channels: make(ChannelSet),
|
316
|
|
- ctime: now,
|
317
|
|
- languages: server.Languages().Default(),
|
318
|
|
- server: server,
|
|
318
|
+ lastSeen: now,
|
|
319
|
+ lastActive: lastActive,
|
|
320
|
+ channels: make(ChannelSet),
|
|
321
|
+ ctime: now,
|
|
322
|
+ languages: server.Languages().Default(),
|
|
323
|
+ server: server,
|
319
|
324
|
|
320
|
325
|
// TODO figure out how to set these on reattach?
|
321
|
326
|
username: "~user",
|
322
|
327
|
rawHostname: server.name,
|
323
|
328
|
realIP: utils.IPv4LoopbackAddress,
|
324
|
329
|
|
325
|
|
- alwaysOn: true,
|
326
|
|
- lastSignoff: lastSignoff,
|
|
330
|
+ alwaysOn: true,
|
327
|
331
|
}
|
328
|
332
|
|
329
|
333
|
client.SetMode(modes.TLS, true)
|
|
@@ -662,25 +666,30 @@ func (client *Client) playReattachMessages(session *Session) {
|
662
|
666
|
channel.autoReplayHistory(client, rb, "")
|
663
|
667
|
rb.Send(true)
|
664
|
668
|
}
|
665
|
|
- if !session.lastSignoff.IsZero() && !hasHistoryCaps {
|
|
669
|
+ if !session.autoreplayMissedSince.IsZero() && !hasHistoryCaps {
|
666
|
670
|
rb := NewResponseBuffer(session)
|
667
|
|
- zncPlayPrivmsgs(client, rb, session.lastSignoff, time.Time{})
|
|
671
|
+ zncPlayPrivmsgs(client, rb, session.autoreplayMissedSince, time.Time{})
|
668
|
672
|
rb.Send(true)
|
669
|
673
|
}
|
670
|
|
- session.lastSignoff = time.Time{}
|
|
674
|
+ session.autoreplayMissedSince = time.Time{}
|
671
|
675
|
}
|
672
|
676
|
|
673
|
677
|
//
|
674
|
678
|
// idle, quit, timers and timeouts
|
675
|
679
|
//
|
676
|
680
|
|
677
|
|
-// Active updates when the client was last 'active' (i.e. the user should be sitting in front of their client).
|
678
|
|
-func (client *Client) Active(session *Session) {
|
|
681
|
+// Touch indicates that we received a line from the client (so the connection is healthy
|
|
682
|
+// at this time, modulo network latency and fakelag). `active` means not a PING or suchlike
|
|
683
|
+// (i.e. the user should be sitting in front of their client).
|
|
684
|
+func (client *Client) Touch(active bool, session *Session) {
|
679
|
685
|
now := time.Now().UTC()
|
680
|
686
|
client.stateMutex.Lock()
|
681
|
687
|
defer client.stateMutex.Unlock()
|
682
|
|
- session.atime = now
|
683
|
|
- client.atime = now
|
|
688
|
+ client.lastSeen = now
|
|
689
|
+ if active {
|
|
690
|
+ client.lastActive = now
|
|
691
|
+ session.lastActive = now
|
|
692
|
+ }
|
684
|
693
|
}
|
685
|
694
|
|
686
|
695
|
// Ping sends the client a PING message.
|
|
@@ -896,7 +905,7 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
|
896
|
905
|
func (client *Client) IdleTime() time.Duration {
|
897
|
906
|
client.stateMutex.RLock()
|
898
|
907
|
defer client.stateMutex.RUnlock()
|
899
|
|
- return time.Since(client.atime)
|
|
908
|
+ return time.Since(client.lastActive)
|
900
|
909
|
}
|
901
|
910
|
|
902
|
911
|
// SignonTime returns this client's signon time as a unix timestamp.
|
|
@@ -1151,12 +1160,6 @@ func (client *Client) Quit(message string, session *Session) {
|
1151
|
1160
|
// has no more sessions.
|
1152
|
1161
|
func (client *Client) destroy(session *Session) {
|
1153
|
1162
|
var sessionsToDestroy []*Session
|
1154
|
|
- var lastSignoff time.Time
|
1155
|
|
- if session != nil {
|
1156
|
|
- lastSignoff = session.idletimer.LastTouch()
|
1157
|
|
- } else {
|
1158
|
|
- lastSignoff = time.Now().UTC()
|
1159
|
|
- }
|
1160
|
1163
|
|
1161
|
1164
|
client.stateMutex.Lock()
|
1162
|
1165
|
details := client.detailsNoMutex()
|
|
@@ -1166,6 +1169,7 @@ func (client *Client) destroy(session *Session) {
|
1166
|
1169
|
sessionRemoved := false
|
1167
|
1170
|
registered := client.registered
|
1168
|
1171
|
alwaysOn := client.alwaysOn
|
|
1172
|
+ saveLastSeen := alwaysOn && client.accountSettings.AutoreplayMissed
|
1169
|
1173
|
var remainingSessions int
|
1170
|
1174
|
if session == nil {
|
1171
|
1175
|
sessionsToDestroy = client.sessions
|
|
@@ -1187,16 +1191,17 @@ func (client *Client) destroy(session *Session) {
|
1187
|
1191
|
// if it's our job to destroy it, don't let anyone else try
|
1188
|
1192
|
client.destroyed = true
|
1189
|
1193
|
}
|
1190
|
|
- if alwaysOn && remainingSessions == 0 {
|
1191
|
|
- client.lastSignoff = lastSignoff
|
1192
|
|
- client.dirtyBits |= IncludeLastSignoff
|
1193
|
|
- } else {
|
1194
|
|
- lastSignoff = time.Time{}
|
|
1194
|
+ if saveLastSeen {
|
|
1195
|
+ client.dirtyBits |= IncludeLastSeen
|
1195
|
1196
|
}
|
1196
|
1197
|
exitedSnomaskSent := client.exitedSnomaskSent
|
1197
|
1198
|
client.stateMutex.Unlock()
|
1198
|
1199
|
|
1199
|
|
- if !lastSignoff.IsZero() {
|
|
1200
|
+ // XXX there is no particular reason to persist this state here rather than
|
|
1201
|
+ // any other place: it would be correct to persist it after every `Touch`. However,
|
|
1202
|
+ // I'm not comfortable introducing that many database writes, and I don't want to
|
|
1203
|
+ // design a throttle.
|
|
1204
|
+ if saveLastSeen {
|
1200
|
1205
|
client.wakeWriter()
|
1201
|
1206
|
}
|
1202
|
1207
|
|
|
@@ -1571,10 +1576,9 @@ func (client *Client) historyStatus(config *Config) (status HistoryStatus, targe
|
1571
|
1576
|
|
1572
|
1577
|
// these are bit flags indicating what part of the client status is "dirty"
|
1573
|
1578
|
// and needs to be read from memory and written to the db
|
1574
|
|
-// TODO add a dirty flag for lastSignoff
|
1575
|
1579
|
const (
|
1576
|
1580
|
IncludeChannels uint = 1 << iota
|
1577
|
|
- IncludeLastSignoff
|
|
1581
|
+ IncludeLastSeen
|
1578
|
1582
|
)
|
1579
|
1583
|
|
1580
|
1584
|
func (client *Client) markDirty(dirtyBits uint) {
|
|
@@ -1629,10 +1633,10 @@ func (client *Client) performWrite() {
|
1629
|
1633
|
}
|
1630
|
1634
|
client.server.accounts.saveChannels(account, channelNames)
|
1631
|
1635
|
}
|
1632
|
|
- if (dirtyBits & IncludeLastSignoff) != 0 {
|
|
1636
|
+ if (dirtyBits & IncludeLastSeen) != 0 {
|
1633
|
1637
|
client.stateMutex.RLock()
|
1634
|
|
- lastSignoff := client.lastSignoff
|
|
1638
|
+ lastSeen := client.lastSeen
|
1635
|
1639
|
client.stateMutex.RUnlock()
|
1636
|
|
- client.server.accounts.saveLastSignoff(account, lastSignoff)
|
|
1640
|
+ client.server.accounts.saveLastSeen(account, lastSeen)
|
1637
|
1641
|
}
|
1638
|
1642
|
}
|