Преглед изворни кода

Merge pull request #1419 from slingamn/alwayson_channelmodes.1

fix #1345
tags/v2.5.0-rc1
Shivaram Lingamneni пре 3 година
родитељ
комит
7e56f62aed
No account linked to committer's email address
6 измењених фајлова са 130 додато и 20 уклоњено
  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 Прегледај датотеку

38
 	keyAccountVHost            = "account.vhost %s"
38
 	keyAccountVHost            = "account.vhost %s"
39
 	keyCertToAccount           = "account.creds.certfp %s"
39
 	keyCertToAccount           = "account.creds.certfp %s"
40
 	keyAccountChannels         = "account.channels %s" // channels registered to the account
40
 	keyAccountChannels         = "account.channels %s" // channels registered to the account
41
-	keyAccountJoinedChannels   = "account.joinedto %s" // channels a persistent client has joined
42
 	keyAccountLastSeen         = "account.lastseen %s"
41
 	keyAccountLastSeen         = "account.lastseen %s"
43
 	keyAccountModes            = "account.modes %s"     // user modes for the always-on client as a string
42
 	keyAccountModes            = "account.modes %s"     // user modes for the always-on client as a string
44
 	keyAccountRealname         = "account.realname %s"  // client realname stored as string
43
 	keyAccountRealname         = "account.realname %s"  // client realname stored as string
45
 	keyAccountSuspended        = "account.suspended %s" // client realname stored as string
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
 	maxCertfpsPerAccount = 5
49
 	maxCertfpsPerAccount = 5
48
 )
50
 )
542
 	return err
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
 	am.server.store.Update(func(tx *buntdb.Tx) error {
555
 	am.server.store.Update(func(tx *buntdb.Tx) error {
549
-		tx.Set(key, channelsStr, nil)
556
+		tx.Set(key, jStr, nil)
550
 		return nil
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
 	var channelsStr string
563
 	var channelsStr string
557
 	am.server.store.View(func(tx *buntdb.Tx) error {
564
 	am.server.store.View(func(tx *buntdb.Tx) error {
558
 		channelsStr, _ = tx.Get(key)
565
 		channelsStr, _ = tx.Get(key)
559
 		return nil
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
 	return
576
 	return
565
 }
577
 }
1454
 	settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
1466
 	settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
1455
 	vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
1467
 	vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
1456
 	channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
1468
 	channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
1457
-	joinedChannelsKey := fmt.Sprintf(keyAccountJoinedChannels, casefoldedAccount)
1469
+	joinedChannelsKey := fmt.Sprintf(keyAccountChannelToModes, casefoldedAccount)
1458
 	lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount)
1470
 	lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount)
1459
 	unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount)
1471
 	unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount)
1460
 	modesKey := fmt.Sprintf(keyAccountModes, casefoldedAccount)
1472
 	modesKey := fmt.Sprintf(keyAccountModes, casefoldedAccount)

+ 27
- 0
irc/channel.go Прегледај датотеку

553
 	return present, modes.AllModes()
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
 func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool {
580
 func (channel *Channel) ClientHasPrivsOver(client *Client, target *Client) bool {
557
 	channel.stateMutex.RLock()
581
 	channel.stateMutex.RLock()
558
 	founder := channel.registeredFounder
582
 	founder := channel.registeredFounder
1383
 	if !exists {
1407
 	if !exists {
1384
 		rb.Add(nil, client.server.name, ERR_USERNOTINCHANNEL, client.Nick(), channel.Name(), client.t("They aren't on that channel"))
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
 	return
1413
 	return
1387
 }
1414
 }
1388
 
1415
 

+ 12
- 6
irc/client.go Прегледај датотеку

404
 	client.run(session)
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
 	now := time.Now().UTC()
408
 	now := time.Now().UTC()
409
 	config := server.Config()
409
 	config := server.Config()
410
 	if lastSeen == nil && account.Settings.AutoreplayMissed {
410
 	if lastSeen == nil && account.Settings.AutoreplayMissed {
463
 	// XXX set this last to avoid confusing SetNick:
463
 	// XXX set this last to avoid confusing SetNick:
464
 	client.registered = true
464
 	client.registered = true
465
 
465
 
466
-	for _, chname := range chnames {
466
+	for chname, modeStr := range channelToModes {
467
 		// XXX we're using isSajoin=true, to make these joins succeed even without channel key
467
 		// XXX we're using isSajoin=true, to make these joins succeed even without channel key
468
 		// this is *probably* ok as long as the persisted memberships are accurate
468
 		// this is *probably* ok as long as the persisted memberships are accurate
469
 		server.channels.Join(client, chname, "", true, nil)
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
 	if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
477
 	if persistenceEnabled(config.Accounts.Multiclient.AutoAway, client.accountSettings.AutoAway) {
1967
 
1972
 
1968
 	if (dirtyBits & IncludeChannels) != 0 {
1973
 	if (dirtyBits & IncludeChannels) != 0 {
1969
 		channels := client.Channels()
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
 	if (dirtyBits & IncludeLastSeen) != 0 {
1982
 	if (dirtyBits & IncludeLastSeen) != 0 {
1977
 		client.server.accounts.saveLastSeen(account, client.copyLastSeen())
1983
 		client.server.accounts.saveLastSeen(account, client.copyLastSeen())

+ 66
- 1
irc/database.go Прегледај датотеку

24
 	// 'version' of the database schema
24
 	// 'version' of the database schema
25
 	keySchemaVersion = "db.version"
25
 	keySchemaVersion = "db.version"
26
 	// latest schema of the db
26
 	// latest schema of the db
27
-	latestDbSchema = 18
27
+	latestDbSchema = 19
28
 
28
 
29
 	keyCloakSecret = "crypto.cloak_secret"
29
 	keyCloakSecret = "crypto.cloak_secret"
30
 )
30
 )
903
 	return nil
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
 func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
966
 func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
907
 	for _, change := range allChanges {
967
 	for _, change := range allChanges {
908
 		if initialVersion == change.InitialVersion {
968
 		if initialVersion == change.InitialVersion {
998
 		TargetVersion:  18,
1058
 		TargetVersion:  18,
999
 		Changer:        schemaChangeV17ToV18,
1059
 		Changer:        schemaChangeV17ToV18,
1000
 	},
1060
 	},
1061
+	{
1062
+		InitialVersion: 18,
1063
+		TargetVersion:  19,
1064
+		Changer:        schemaChangeV18To19,
1065
+	},
1001
 }
1066
 }

+ 2
- 2
irc/getters.go Прегледај датотеку

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

+ 1
- 1
irc/import.go Прегледај датотеку

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

Loading…
Откажи
Сачувај