浏览代码

Merge pull request #1036 from slingamn/account_persistence.3

last round of feature changes
tags/v2.1.0-rc1
Shivaram Lingamneni 4 年前
父节点
当前提交
06b2cb2efc
没有帐户链接到提交者的电子邮件
共有 11 个文件被更改,包括 146 次插入30 次删除
  1. 3
    0
      conventional.yaml
  2. 26
    1
      irc/accounts.go
  3. 44
    4
      irc/client.go
  4. 20
    20
      irc/client_lookup_set.go
  5. 1
    0
      irc/config.go
  6. 8
    2
      irc/getters.go
  7. 5
    2
      irc/handlers.go
  8. 4
    0
      irc/modes.go
  9. 5
    1
      irc/nickname.go
  10. 27
    0
      irc/nickserv.go
  11. 3
    0
      oragono.yaml

+ 3
- 0
conventional.yaml 查看文件

@@ -413,6 +413,9 @@ accounts:
413 413
         # "disabled", "opt-in", "opt-out", or "mandatory".
414 414
         always-on: "disabled"
415 415
 
416
+        # whether to mark always-on clients away when they have no active connections:
417
+        auto-away: "opt-in"
418
+
416 419
     # vhosts controls the assignment of vhosts (strings displayed in place of the user's
417 420
     # hostname/IP) by the HostServ service
418 421
     vhosts:

+ 26
- 1
irc/accounts.go 查看文件

@@ -19,6 +19,7 @@ import (
19 19
 	"github.com/oragono/oragono/irc/connection_limits"
20 20
 	"github.com/oragono/oragono/irc/email"
21 21
 	"github.com/oragono/oragono/irc/ldap"
22
+	"github.com/oragono/oragono/irc/modes"
22 23
 	"github.com/oragono/oragono/irc/passwd"
23 24
 	"github.com/oragono/oragono/irc/utils"
24 25
 	"github.com/tidwall/buntdb"
@@ -40,6 +41,7 @@ const (
40 41
 	keyAccountChannels         = "account.channels %s" // channels registered to the account
41 42
 	keyAccountJoinedChannels   = "account.joinedto %s" // channels a persistent client has joined
42 43
 	keyAccountLastSeen         = "account.lastseen %s"
44
+	keyAccountModes            = "account.modes %s" // user modes for the always-on client as a string
43 45
 
44 46
 	keyVHostQueueAcctToId = "vhostQueue %s"
45 47
 	vhostRequestIdx       = "vhostQueue"
@@ -127,7 +129,7 @@ func (am *AccountManager) createAlwaysOnClients(config *Config) {
127 129
 		account, err := am.LoadAccount(accountName)
128 130
 		if err == nil && account.Verified &&
129 131
 			persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, account.Settings.AlwaysOn) {
130
-			am.server.AddAlwaysOnClient(account, am.loadChannels(accountName), am.loadLastSeen(accountName))
132
+			am.server.AddAlwaysOnClient(account, am.loadChannels(accountName), am.loadLastSeen(accountName), am.loadModes(accountName))
131 133
 		}
132 134
 	}
133 135
 }
@@ -594,6 +596,28 @@ func (am *AccountManager) loadChannels(account string) (channels []string) {
594 596
 	return
595 597
 }
596 598
 
599
+func (am *AccountManager) saveModes(account string, uModes modes.Modes) {
600
+	modeStr := uModes.String()
601
+	key := fmt.Sprintf(keyAccountModes, account)
602
+	am.server.store.Update(func(tx *buntdb.Tx) error {
603
+		tx.Set(key, modeStr, nil)
604
+		return nil
605
+	})
606
+}
607
+
608
+func (am *AccountManager) loadModes(account string) (uModes modes.Modes) {
609
+	key := fmt.Sprintf(keyAccountModes, account)
610
+	var modeStr string
611
+	am.server.store.View(func(tx *buntdb.Tx) error {
612
+		modeStr, _ = tx.Get(key)
613
+		return nil
614
+	})
615
+	for _, m := range modeStr {
616
+		uModes = append(uModes, modes.Mode(m))
617
+	}
618
+	return
619
+}
620
+
597 621
 func (am *AccountManager) saveLastSeen(account string, lastSeen time.Time) {
598 622
 	key := fmt.Sprintf(keyAccountLastSeen, account)
599 623
 	var val string
@@ -1884,6 +1908,7 @@ type AccountSettings struct {
1884 1908
 	AlwaysOn         PersistentStatus
1885 1909
 	AutoreplayMissed bool
1886 1910
 	DMHistory        HistoryStatus
1911
+	AutoAway         PersistentStatus
1887 1912
 }
1888 1913
 
1889 1914
 // ClientAccount represents a user account.

+ 44
- 4
irc/client.go 查看文件

@@ -48,6 +48,7 @@ type Client struct {
48 48
 	accountRegDate     time.Time
49 49
 	accountSettings    AccountSettings
50 50
 	away               bool
51
+	autoAway           bool
51 52
 	awayMessage        string
52 53
 	brbTimer           BrbTimer
53 54
 	channels           ChannelSet
@@ -359,7 +360,7 @@ func (server *Server) RunClient(conn IRCConn) {
359 360
 	client.run(session)
360 361
 }
361 362
 
362
-func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen time.Time) {
363
+func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen time.Time, uModes modes.Modes) {
363 364
 	now := time.Now().UTC()
364 365
 	config := server.Config()
365 366
 	if lastSeen.IsZero() {
@@ -382,9 +383,10 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
382 383
 		alwaysOn: true,
383 384
 	}
384 385
 
385
-	ApplyUserModeChanges(client, config.Accounts.defaultUserModes, false, nil)
386
-
387 386
 	client.SetMode(modes.TLS, true)
387
+	for _, m := range uModes {
388
+		client.SetMode(m, true)
389
+	}
388 390
 	client.writerSemaphore.Initialize(1)
389 391
 	client.history.Initialize(0, 0)
390 392
 	client.brbTimer.Initialize(client)
@@ -393,7 +395,7 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
393 395
 
394 396
 	client.resizeHistory(config)
395 397
 
396
-	_, err := server.clients.SetNick(client, nil, account.Name)
398
+	_, err, _ := server.clients.SetNick(client, nil, account.Name)
397 399
 	if err != nil {
398 400
 		server.logger.Error("internal", "could not establish always-on client", account.Name, err.Error())
399 401
 		return
@@ -409,6 +411,12 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
409 411
 		// this is *probably* ok as long as the persisted memberships are accurate
410 412
 		server.channels.Join(client, chname, "", true, nil)
411 413
 	}
414
+
415
+	if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
416
+		client.autoAway = true
417
+		client.away = true
418
+		client.awayMessage = client.t("User is currently disconnected")
419
+	}
412 420
 }
413 421
 
414 422
 func (client *Client) resizeHistory(config *Config) {
@@ -588,6 +596,7 @@ func (client *Client) run(session *Session) {
588 596
 
589 597
 	isReattach := client.Registered()
590 598
 	if isReattach {
599
+		session.idletimer.Touch()
591 600
 		if session.resumeDetails != nil {
592 601
 			session.playResume()
593 602
 			session.resumeDetails = nil
@@ -1187,6 +1196,7 @@ func (client *Client) Quit(message string, session *Session) {
1187 1196
 // otherwise, destroys one specific session, only destroying the client if it
1188 1197
 // has no more sessions.
1189 1198
 func (client *Client) destroy(session *Session) {
1199
+	config := client.server.Config()
1190 1200
 	var sessionsToDestroy []*Session
1191 1201
 
1192 1202
 	client.stateMutex.Lock()
@@ -1225,6 +1235,17 @@ func (client *Client) destroy(session *Session) {
1225 1235
 		client.dirtyBits |= IncludeLastSeen
1226 1236
 	}
1227 1237
 	exitedSnomaskSent := client.exitedSnomaskSent
1238
+
1239
+	autoAway := false
1240
+	var awayMessage string
1241
+	if alwaysOn && remainingSessions == 0 && persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
1242
+		autoAway = true
1243
+		client.autoAway = true
1244
+		client.away = true
1245
+		awayMessage = config.languageManager.Translate(client.languages, `Disconnected from the server`)
1246
+		client.awayMessage = awayMessage
1247
+	}
1248
+
1228 1249
 	client.stateMutex.Unlock()
1229 1250
 
1230 1251
 	// XXX there is no particular reason to persist this state here rather than
@@ -1272,6 +1293,10 @@ func (client *Client) destroy(session *Session) {
1272 1293
 		client.server.stats.Remove(registered, invisible, operator)
1273 1294
 	}
1274 1295
 
1296
+	if autoAway {
1297
+		dispatchAwayNotify(client, true, awayMessage)
1298
+	}
1299
+
1275 1300
 	if !shouldDestroy {
1276 1301
 		return
1277 1302
 	}
@@ -1610,6 +1635,7 @@ func (client *Client) historyStatus(config *Config) (status HistoryStatus, targe
1610 1635
 const (
1611 1636
 	IncludeChannels uint = 1 << iota
1612 1637
 	IncludeLastSeen
1638
+	IncludeUserModes
1613 1639
 )
1614 1640
 
1615 1641
 func (client *Client) markDirty(dirtyBits uint) {
@@ -1668,4 +1694,18 @@ func (client *Client) performWrite() {
1668 1694
 	if (dirtyBits & IncludeLastSeen) != 0 {
1669 1695
 		client.server.accounts.saveLastSeen(account, lastSeen)
1670 1696
 	}
1697
+	if (dirtyBits & IncludeUserModes) != 0 {
1698
+		uModes := make(modes.Modes, 0, len(modes.SupportedUserModes))
1699
+		for _, m := range modes.SupportedUserModes {
1700
+			switch m {
1701
+			case modes.Operator, modes.ServerNotice:
1702
+				// these can't be persisted because they depend on the operator block
1703
+			default:
1704
+				if client.HasMode(m) {
1705
+					uModes = append(uModes, m)
1706
+				}
1707
+			}
1708
+		}
1709
+		client.server.accounts.saveModes(account, uModes)
1710
+	}
1671 1711
 }

+ 20
- 20
irc/client_lookup_set.go 查看文件

@@ -103,7 +103,7 @@ func (clients *ClientManager) Resume(oldClient *Client, session *Session) (err e
103 103
 		return errNickMissing
104 104
 	}
105 105
 
106
-	success, _, _ := oldClient.AddSession(session)
106
+	success, _, _, _ := oldClient.AddSession(session)
107 107
 	if !success {
108 108
 		return errNickMissing
109 109
 	}
@@ -112,7 +112,7 @@ func (clients *ClientManager) Resume(oldClient *Client, session *Session) (err e
112 112
 }
113 113
 
114 114
 // SetNick sets a client's nickname, validating it against nicknames in use
115
-func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string) (setNick string, err error) {
115
+func (clients *ClientManager) SetNick(client *Client, session *Session, newNick string) (setNick string, err error, returnedFromAway bool) {
116 116
 	config := client.server.Config()
117 117
 
118 118
 	var newCfNick, newSkeleton string
@@ -134,24 +134,24 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
134 134
 
135 135
 	if useAccountName {
136 136
 		if registered && newNick != accountName && newNick != "" {
137
-			return "", errNickAccountMismatch
137
+			return "", errNickAccountMismatch, false
138 138
 		}
139 139
 		newNick = accountName
140 140
 		newCfNick = account
141 141
 		newSkeleton, err = Skeleton(newNick)
142 142
 		if err != nil {
143
-			return "", errNicknameInvalid
143
+			return "", errNicknameInvalid, false
144 144
 		}
145 145
 	} else {
146 146
 		newNick = strings.TrimSpace(newNick)
147 147
 		if len(newNick) == 0 {
148
-			return "", errNickMissing
148
+			return "", errNickMissing, false
149 149
 		}
150 150
 
151 151
 		if account == "" && config.Accounts.NickReservation.ForceGuestFormat {
152 152
 			newCfNick, err = CasefoldName(newNick)
153 153
 			if err != nil {
154
-				return "", errNicknameInvalid
154
+				return "", errNicknameInvalid, false
155 155
 			}
156 156
 			if !config.Accounts.NickReservation.guestRegexpFolded.MatchString(newCfNick) {
157 157
 				newNick = strings.Replace(config.Accounts.NickReservation.GuestFormat, "*", newNick, 1)
@@ -163,23 +163,23 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
163 163
 			newCfNick, err = CasefoldName(newNick)
164 164
 		}
165 165
 		if err != nil {
166
-			return "", errNicknameInvalid
166
+			return "", errNicknameInvalid, false
167 167
 		}
168 168
 		if len(newNick) > config.Limits.NickLen || len(newCfNick) > config.Limits.NickLen {
169
-			return "", errNicknameInvalid
169
+			return "", errNicknameInvalid, false
170 170
 		}
171 171
 		newSkeleton, err = Skeleton(newNick)
172 172
 		if err != nil {
173
-			return "", errNicknameInvalid
173
+			return "", errNicknameInvalid, false
174 174
 		}
175 175
 
176 176
 		if restrictedCasefoldedNicks[newCfNick] || restrictedSkeletons[newSkeleton] {
177
-			return "", errNicknameInvalid
177
+			return "", errNicknameInvalid, false
178 178
 		}
179 179
 
180 180
 		reservedAccount, method := client.server.accounts.EnforcementStatus(newCfNick, newSkeleton)
181 181
 		if method == NickEnforcementStrict && reservedAccount != "" && reservedAccount != account {
182
-			return "", errNicknameReserved
182
+			return "", errNicknameReserved, false
183 183
 		}
184 184
 	}
185 185
 
@@ -204,20 +204,20 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
204 204
 	if currentClient != nil && currentClient != client && session != nil {
205 205
 		// these conditions forbid reattaching to an existing session:
206 206
 		if registered || !bouncerAllowed || account == "" || account != currentClient.Account() {
207
-			return "", errNicknameInUse
207
+			return "", errNicknameInUse, false
208 208
 		}
209 209
 		// check TLS modes
210 210
 		if client.HasMode(modes.TLS) != currentClient.HasMode(modes.TLS) {
211 211
 			if useAccountName {
212 212
 				// #955: this is fatal because they can't fix it by trying a different nick
213
-				return "", errInsecureReattach
213
+				return "", errInsecureReattach, false
214 214
 			} else {
215
-				return "", errNicknameInUse
215
+				return "", errNicknameInUse, false
216 216
 			}
217 217
 		}
218
-		reattachSuccessful, numSessions, lastSeen := currentClient.AddSession(session)
218
+		reattachSuccessful, numSessions, lastSeen, back := currentClient.AddSession(session)
219 219
 		if !reattachSuccessful {
220
-			return "", errNicknameInUse
220
+			return "", errNicknameInUse, false
221 221
 		}
222 222
 		if numSessions == 1 {
223 223
 			invisible := currentClient.HasMode(modes.Invisible)
@@ -232,24 +232,24 @@ func (clients *ClientManager) SetNick(client *Client, session *Session, newNick
232 232
 		// for performance reasons
233 233
 		currentClient.SetNames("user", realname, true)
234 234
 		// successful reattach!
235
-		return newNick, nil
235
+		return newNick, nil, back
236 236
 	} else if currentClient == client && currentClient.Nick() == newNick {
237 237
 		// see #1019: normally no-op nick changes are caught earlier, by performNickChange,
238 238
 		// but they are not detected there when force-guest-format is enabled (because
239 239
 		// the proposed nickname is e.g. alice and the current nickname is Guest-alice)
240
-		return "", errNoop
240
+		return "", errNoop, false
241 241
 	}
242 242
 	// analogous checks for skeletons
243 243
 	skeletonHolder := clients.bySkeleton[newSkeleton]
244 244
 	if skeletonHolder != nil && skeletonHolder != client {
245
-		return "", errNicknameInUse
245
+		return "", errNicknameInUse, false
246 246
 	}
247 247
 
248 248
 	clients.removeInternal(client)
249 249
 	clients.byNick[newCfNick] = client
250 250
 	clients.bySkeleton[newSkeleton] = client
251 251
 	client.updateNick(newNick, newCfNick, newSkeleton)
252
-	return newNick, nil
252
+	return newNick, nil, false
253 253
 }
254 254
 
255 255
 func (clients *ClientManager) AllClients() (result []*Client) {

+ 1
- 0
irc/config.go 查看文件

@@ -221,6 +221,7 @@ type MulticlientConfig struct {
221 221
 	Enabled          bool
222 222
 	AllowedByDefault bool             `yaml:"allowed-by-default"`
223 223
 	AlwaysOn         PersistentStatus `yaml:"always-on"`
224
+	AutoAway         PersistentStatus `yaml:"auto-away"`
224 225
 }
225 226
 
226 227
 type throttleConfig struct {

+ 8
- 2
irc/getters.go 查看文件

@@ -89,7 +89,7 @@ func (client *Client) AllSessionData(currentSession *Session) (data []SessionDat
89 89
 	return
90 90
 }
91 91
 
92
-func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time) {
92
+func (client *Client) AddSession(session *Session) (success bool, numSessions int, lastSeen time.Time, back bool) {
93 93
 	client.stateMutex.Lock()
94 94
 	defer client.stateMutex.Unlock()
95 95
 
@@ -106,7 +106,13 @@ func (client *Client) AddSession(session *Session) (success bool, numSessions in
106 106
 		lastSeen = client.lastSeen
107 107
 	}
108 108
 	client.sessions = newSessions
109
-	return true, len(client.sessions), lastSeen
109
+	if client.autoAway {
110
+		back = true
111
+		client.autoAway = false
112
+		client.away = false
113
+		client.awayMessage = ""
114
+	}
115
+	return true, len(client.sessions), lastSeen, back
110 116
 }
111 117
 
112 118
 func (client *Client) removeSession(session *Session) (success bool, length int) {

+ 5
- 2
irc/handlers.go 查看文件

@@ -316,6 +316,11 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
316 316
 		rb.Add(nil, server.name, RPL_UNAWAY, client.nick, client.t("You are no longer marked as being away"))
317 317
 	}
318 318
 
319
+	dispatchAwayNotify(client, isAway, awayMessage)
320
+	return false
321
+}
322
+
323
+func dispatchAwayNotify(client *Client, isAway bool, awayMessage string) {
319 324
 	// dispatch away-notify
320 325
 	details := client.Details()
321 326
 	for session := range client.Friends(caps.AwayNotify) {
@@ -325,8 +330,6 @@ func awayHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
325 330
 			session.sendFromClientInternal(false, time.Time{}, "", details.nickMask, details.account, nil, "AWAY")
326 331
 		}
327 332
 	}
328
-
329
-	return false
330 333
 }
331 334
 
332 335
 // BATCH {+,-}reference-tag type [params...]

+ 4
- 0
irc/modes.go 查看文件

@@ -102,6 +102,10 @@ func ApplyUserModeChanges(client *Client, changes modes.ModeChanges, force bool,
102 102
 		// can't do anything to TLS mode
103 103
 	}
104 104
 
105
+	if len(applied) != 0 {
106
+		client.markDirty(IncludeUserModes)
107
+	}
108
+
105 109
 	// return the changes we could actually apply
106 110
 	return applied
107 111
 }

+ 5
- 1
irc/nickname.go 查看文件

@@ -33,7 +33,7 @@ func performNickChange(server *Server, client *Client, target *Client, session *
33 33
 	hadNick := details.nick != "*"
34 34
 	origNickMask := details.nickMask
35 35
 
36
-	assignedNickname, err := client.server.clients.SetNick(target, session, nickname)
36
+	assignedNickname, err, back := client.server.clients.SetNick(target, session, nickname)
37 37
 	if err == errNicknameInUse {
38 38
 		rb.Add(nil, server.name, ERR_NICKNAMEINUSE, currentNick, utils.SafeErrorParam(nickname), client.t("Nickname is already in use"))
39 39
 	} else if err == errNicknameReserved {
@@ -80,6 +80,10 @@ func performNickChange(server *Server, client *Client, target *Client, session *
80 80
 		}
81 81
 	}
82 82
 
83
+	if back {
84
+		dispatchAwayNotify(session.client, false, "")
85
+	}
86
+
83 87
 	for _, channel := range client.Channels() {
84 88
 		channel.AddHistoryItem(histItem, details.account)
85 89
 	}

+ 27
- 0
irc/nickserv.go 查看文件

@@ -289,6 +289,10 @@ how the history of your direct messages is stored. Your options are:
289 289
 2. 'ephemeral'  [a limited amount of temporary history, not stored on disk]
290 290
 3. 'on'         [history stored in a permanent database, if available]
291 291
 4. 'default'    [use the server default]`,
292
+				`$bAUTO-AWAY$b
293
+'auto-away' is only effective for always-on clients. If enabled, you will
294
+automatically be marked away when all your sessions are disconnected, and
295
+automatically return from away when you connect again.`,
292 296
 			},
293 297
 			authRequired: true,
294 298
 			enabled:      servCmdRequiresAuthEnabled,
@@ -412,6 +416,18 @@ func displaySetting(settingName string, settings AccountSettings, client *Client
412 416
 		} else {
413 417
 			nsNotice(rb, client.t("Your account is not configured to receive autoreplayed missed messages"))
414 418
 		}
419
+	case "auto-away":
420
+		stored := settings.AutoAway
421
+		alwaysOn := persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, settings.AlwaysOn)
422
+		actual := persistenceEnabled(config.Accounts.Multiclient.AutoAway, settings.AutoAway)
423
+		nsNotice(rb, fmt.Sprintf(client.t("Your stored auto-away setting is: %s"), persistentStatusToString(stored)))
424
+		if actual && alwaysOn {
425
+			nsNotice(rb, client.t("Given current server settings, auto-away is enabled for your client"))
426
+		} else if actual && !alwaysOn {
427
+			nsNotice(rb, client.t("Because your client is not always-on, auto-away is disabled"))
428
+		} else if !actual {
429
+			nsNotice(rb, client.t("Given current server settings, auto-away is disabled for your client"))
430
+		}
415 431
 	case "dm-history":
416 432
 		effectiveValue := historyEnabled(config.History.Persistent.DirectMessages, settings.DMHistory)
417 433
 		csNotice(rb, fmt.Sprintf(client.t("Your stored direct message history setting is: %s"), historyStatusToString(settings.DMHistory)))
@@ -530,6 +546,17 @@ func nsSetHandler(server *Server, client *Client, command string, params []strin
530 546
 				return
531 547
 			}
532 548
 		}
549
+	case "auto-away":
550
+		var newValue PersistentStatus
551
+		newValue, err = persistentStatusFromString(params[1])
552
+		// "opt-in" and "opt-out" don't make sense as user preferences
553
+		if err == nil && newValue != PersistentOptIn && newValue != PersistentOptOut {
554
+			munger = func(in AccountSettings) (out AccountSettings, err error) {
555
+				out = in
556
+				out.AutoAway = newValue
557
+				return
558
+			}
559
+		}
533 560
 	case "dm-history":
534 561
 		var newValue HistoryStatus
535 562
 		newValue, err = historyStatusFromString(params[1])

+ 3
- 0
oragono.yaml 查看文件

@@ -439,6 +439,9 @@ accounts:
439 439
         # "disabled", "opt-in", "opt-out", or "mandatory".
440 440
         always-on: "opt-in"
441 441
 
442
+        # whether to mark always-on clients away when they have no active connections:
443
+        auto-away: "opt-in"
444
+
442 445
     # vhosts controls the assignment of vhosts (strings displayed in place of the user's
443 446
     # hostname/IP) by the HostServ service
444 447
     vhosts:

正在加载...
取消
保存