Browse Source

Merge pull request #1419 from slingamn/alwayson_channelmodes.1

fix #1345
tags/v2.5.0-rc1
Shivaram Lingamneni 3 years ago
parent
commit
7e56f62aed
No account linked to committer's email address
6 changed files with 130 additions and 20 deletions
  1. 22
    10
      irc/accounts.go
  2. 27
    0
      irc/channel.go
  3. 12
    6
      irc/client.go
  4. 66
    1
      irc/database.go
  5. 2
    2
      irc/getters.go
  6. 1
    1
      irc/import.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())

+ 66
- 1
irc/database.go View File

@@ -24,7 +24,7 @@ const (
24 24
 	// 'version' of the database schema
25 25
 	keySchemaVersion = "db.version"
26 26
 	// latest schema of the db
27
-	latestDbSchema = 18
27
+	latestDbSchema = 19
28 28
 
29 29
 	keyCloakSecret = "crypto.cloak_secret"
30 30
 )
@@ -903,6 +903,66 @@ func schemaChangeV17ToV18(config *Config, tx *buntdb.Tx) error {
903 903
 	return nil
904 904
 }
905 905
 
906
+// #1345: persist the channel-user modes of always-on clients
907
+func schemaChangeV18To19(config *Config, tx *buntdb.Tx) error {
908
+	channelToAmodesCache := make(map[string]map[string]modes.Mode)
909
+	joinedto := "account.joinedto "
910
+	var accounts []string
911
+	var channels [][]string
912
+	tx.AscendGreaterOrEqual("", joinedto, func(key, value string) bool {
913
+		if !strings.HasPrefix(key, joinedto) {
914
+			return false
915
+		}
916
+		accounts = append(accounts, strings.TrimPrefix(key, joinedto))
917
+		var ch []string
918
+		if value != "" {
919
+			ch = strings.Split(value, ",")
920
+		}
921
+		channels = append(channels, ch)
922
+		return true
923
+	})
924
+
925
+	for i := 0; i < len(accounts); i++ {
926
+		account := accounts[i]
927
+		channels := channels[i]
928
+		tx.Delete(joinedto + account)
929
+		newValue := make(map[string]string, len(channels))
930
+		for _, channel := range channels {
931
+			chcfname, err := CasefoldChannel(channel)
932
+			if err != nil {
933
+				continue
934
+			}
935
+			// get amodes from the channelToAmodesCache, fill if necessary
936
+			amodes, ok := channelToAmodesCache[chcfname]
937
+			if !ok {
938
+				amodeStr, _ := tx.Get("channel.accounttoumode " + chcfname)
939
+				if amodeStr != "" {
940
+					jErr := json.Unmarshal([]byte(amodeStr), &amodes)
941
+					if jErr != nil {
942
+						log.Printf("error retrieving amodes for %s: %v\n", channel, jErr)
943
+						amodes = nil
944
+					}
945
+				}
946
+				// setting/using the nil value here is ok
947
+				channelToAmodesCache[chcfname] = amodes
948
+			}
949
+			if mode, ok := amodes[account]; ok {
950
+				newValue[channel] = string(mode)
951
+			} else {
952
+				newValue[channel] = ""
953
+			}
954
+		}
955
+		newValueBytes, jErr := json.Marshal(newValue)
956
+		if jErr != nil {
957
+			log.Printf("couldn't serialize new mode values for v19: %v\n", jErr)
958
+			continue
959
+		}
960
+		tx.Set("account.channeltomodes "+account, string(newValueBytes), nil)
961
+	}
962
+
963
+	return nil
964
+}
965
+
906 966
 func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
907 967
 	for _, change := range allChanges {
908 968
 		if initialVersion == change.InitialVersion {
@@ -998,4 +1058,9 @@ var allChanges = []SchemaChange{
998 1058
 		TargetVersion:  18,
999 1059
 		Changer:        schemaChangeV17ToV18,
1000 1060
 	},
1061
+	{
1062
+		InitialVersion: 18,
1063
+		TargetVersion:  19,
1064
+		Changer:        schemaChangeV18To19,
1065
+	},
1001 1066
 }

+ 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
 

+ 1
- 1
irc/import.go View File

@@ -20,7 +20,7 @@ const (
20 20
 	// XXX instead of referencing, e.g., keyAccountExists, we should write in the string literal
21 21
 	// (to ensure that no matter what code changes happen elsewhere, we're still producing a
22 22
 	// db of the hardcoded version)
23
-	importDBSchemaVersion = 18
23
+	importDBSchemaVersion = 19
24 24
 )
25 25
 
26 26
 type userImport struct {

Loading…
Cancel
Save