Browse Source

fix #1345

Store the channel-user modes of always-on clients along with their
channel memberships, restore them on server startup. This will coexist
alongside /CS AMODE, which autoapplies modes to clients on join regardless
of their always-on status.
tags/v2.5.0-rc1
Shivaram Lingamneni 3 years ago
parent
commit
51f279289d
4 changed files with 63 additions and 18 deletions
  1. 22
    10
      irc/accounts.go
  2. 27
    0
      irc/channel.go
  3. 12
    6
      irc/client.go
  4. 2
    2
      irc/getters.go

+ 22
- 10
irc/accounts.go View File

@@ -38,11 +38,13 @@ const (
38 38
 	keyAccountVHost            = "account.vhost %s"
39 39
 	keyCertToAccount           = "account.creds.certfp %s"
40 40
 	keyAccountChannels         = "account.channels %s" // channels registered to the account
41
-	keyAccountJoinedChannels   = "account.joinedto %s" // channels a persistent client has joined
42 41
 	keyAccountLastSeen         = "account.lastseen %s"
43 42
 	keyAccountModes            = "account.modes %s"     // user modes for the always-on client as a string
44 43
 	keyAccountRealname         = "account.realname %s"  // client realname stored as string
45 44
 	keyAccountSuspended        = "account.suspended %s" // client realname stored as string
45
+	// for an always-on client, a map of channel names they're in to their current modes
46
+	// (not to be confused with their amodes, which a non-always-on client can have):
47
+	keyAccountChannelToModes = "account.channeltomodes %s"
46 48
 
47 49
 	maxCertfpsPerAccount = 5
48 50
 )
@@ -542,24 +544,34 @@ func (am *AccountManager) setPassword(account string, password string, hasPrivs
542 544
 	return err
543 545
 }
544 546
 
545
-func (am *AccountManager) saveChannels(account string, channels []string) {
546
-	channelsStr := strings.Join(channels, ",")
547
-	key := fmt.Sprintf(keyAccountJoinedChannels, account)
547
+func (am *AccountManager) saveChannels(account string, channelToModes map[string]string) {
548
+	j, err := json.Marshal(channelToModes)
549
+	if err != nil {
550
+		am.server.logger.Error("internal", "couldn't marshal channel-to-modes", account, err.Error())
551
+		return
552
+	}
553
+	jStr := string(j)
554
+	key := fmt.Sprintf(keyAccountChannelToModes, account)
548 555
 	am.server.store.Update(func(tx *buntdb.Tx) error {
549
-		tx.Set(key, channelsStr, nil)
556
+		tx.Set(key, jStr, nil)
550 557
 		return nil
551 558
 	})
552 559
 }
553 560
 
554
-func (am *AccountManager) loadChannels(account string) (channels []string) {
555
-	key := fmt.Sprintf(keyAccountJoinedChannels, account)
561
+func (am *AccountManager) loadChannels(account string) (channelToModes map[string]string) {
562
+	key := fmt.Sprintf(keyAccountChannelToModes, account)
556 563
 	var channelsStr string
557 564
 	am.server.store.View(func(tx *buntdb.Tx) error {
558 565
 		channelsStr, _ = tx.Get(key)
559 566
 		return nil
560 567
 	})
561
-	if channelsStr != "" {
562
-		return strings.Split(channelsStr, ",")
568
+	if channelsStr == "" {
569
+		return nil
570
+	}
571
+	err := json.Unmarshal([]byte(channelsStr), &channelToModes)
572
+	if err != nil {
573
+		am.server.logger.Error("internal", "couldn't marshal channel-to-modes", account, err.Error())
574
+		return nil
563 575
 	}
564 576
 	return
565 577
 }
@@ -1454,7 +1466,7 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
1454 1466
 	settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
1455 1467
 	vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
1456 1468
 	channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
1457
-	joinedChannelsKey := fmt.Sprintf(keyAccountJoinedChannels, casefoldedAccount)
1469
+	joinedChannelsKey := fmt.Sprintf(keyAccountChannelToModes, casefoldedAccount)
1458 1470
 	lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount)
1459 1471
 	unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount)
1460 1472
 	modesKey := fmt.Sprintf(keyAccountModes, casefoldedAccount)

+ 27
- 0
irc/channel.go View File

@@ -553,6 +553,30 @@ func (channel *Channel) ClientStatus(client *Client) (present bool, cModes modes
553 553
 	return present, modes.AllModes()
554 554
 }
555 555
 
556
+// helper for persisting channel-user modes for always-on clients;
557
+// return the channel name and all channel-user modes for a client
558
+func (channel *Channel) nameAndModes(client *Client) (chname string, modeStr string) {
559
+	channel.stateMutex.RLock()
560
+	defer channel.stateMutex.RUnlock()
561
+	chname = channel.name
562
+	modeStr = channel.members[client].String()
563
+	return
564
+}
565
+
566
+// overwrite any existing channel-user modes with the stored ones
567
+func (channel *Channel) setModesForClient(client *Client, modeStr string) {
568
+	newModes := modes.NewModeSet()
569
+	for _, mode := range modeStr {
570
+		newModes.SetMode(modes.Mode(mode), true)
571
+	}
572
+	channel.stateMutex.Lock()
573
+	defer channel.stateMutex.Unlock()
574
+	if _, ok := channel.members[client]; !ok {
575
+		return
576
+	}
577
+	channel.members[client] = newModes
578
+}
579
+
556 580
 func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool {
557 581
 	channel.stateMutex.RLock()
558 582
 	founder := channel.registeredFounder
@@ -1383,6 +1407,9 @@ func (channel *Channel) applyModeToMember(client *Client, change modes.ModeChang
1383 1407
 	if !exists {
1384 1408
 		rb.Add(nil, client.server.name, ERR_USERNOTINCHANNEL, client.Nick(), channel.Name(), client.t("They aren't on that channel"))
1385 1409
 	}
1410
+	if applied {
1411
+		target.markDirty(IncludeChannels)
1412
+	}
1386 1413
 	return
1387 1414
 }
1388 1415
 

+ 12
- 6
irc/client.go View File

@@ -404,7 +404,7 @@ func (server *Server) RunClient(conn IRCConn) {
404 404
 	client.run(session)
405 405
 }
406 406
 
407
-func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string, lastSeen map[string]time.Time, uModes modes.Modes, realname string) {
407
+func (server *Server) AddAlwaysOnClient(account ClientAccount, channelToModes map[string]string, lastSeen map[string]time.Time, uModes modes.Modes, realname string) {
408 408
 	now := time.Now().UTC()
409 409
 	config := server.Config()
410 410
 	if lastSeen == nil && account.Settings.AutoreplayMissed {
@@ -463,10 +463,15 @@ func (server *Server) AddAlwaysOnClient(account ClientAccount, chnames []string,
463 463
 	// XXX set this last to avoid confusing SetNick:
464 464
 	client.registered = true
465 465
 
466
-	for _, chname := range chnames {
466
+	for chname, modeStr := range channelToModes {
467 467
 		// XXX we're using isSajoin=true, to make these joins succeed even without channel key
468 468
 		// this is *probably* ok as long as the persisted memberships are accurate
469 469
 		server.channels.Join(client, chname, "", true, nil)
470
+		if channel := server.channels.Get(chname); channel != nil {
471
+			channel.setModesForClient(client, modeStr)
472
+		} else {
473
+			server.logger.Error("internal", "could not create channel", chname)
474
+		}
470 475
 	}
471 476
 
472 477
 	if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
@@ -1967,11 +1972,12 @@ func (client *Client) performWrite(additionalDirtyBits uint) {
1967 1972
 
1968 1973
 	if (dirtyBits & IncludeChannels) != 0 {
1969 1974
 		channels := client.Channels()
1970
-		channelNames := make([]string, len(channels))
1971
-		for i, channel := range channels {
1972
-			channelNames[i] = channel.Name()
1975
+		channelToModes := make(map[string]string, len(channels))
1976
+		for _, channel := range channels {
1977
+			chname, modes := channel.nameAndModes(client)
1978
+			channelToModes[chname] = modes
1973 1979
 		}
1974
-		client.server.accounts.saveChannels(account, channelNames)
1980
+		client.server.accounts.saveChannels(account, channelToModes)
1975 1981
 	}
1976 1982
 	if (dirtyBits & IncludeLastSeen) != 0 {
1977 1983
 		client.server.accounts.saveLastSeen(account, client.copyLastSeen())

+ 2
- 2
irc/getters.go View File

@@ -211,9 +211,9 @@ func (client *Client) SetAway(away bool, awayMessage string) (changed bool) {
211 211
 }
212 212
 
213 213
 func (client *Client) AlwaysOn() (alwaysOn bool) {
214
-	client.stateMutex.Lock()
214
+	client.stateMutex.RLock()
215 215
 	alwaysOn = client.registered && client.alwaysOn
216
-	client.stateMutex.Unlock()
216
+	client.stateMutex.RUnlock()
217 217
 	return
218 218
 }
219 219
 

Loading…
Cancel
Save