bridgev2/matrix: use cached member list if available

This commit is contained in:
Tulir Asokan 2024-08-20 16:19:49 +03:00
commit 9e1a8cd56e
8 changed files with 123 additions and 18 deletions

View file

@ -540,7 +540,12 @@ func (br *Connector) GetPowerLevels(ctx context.Context, roomID id.RoomID) (*eve
}
func (br *Connector) GetMembers(ctx context.Context, roomID id.RoomID) (map[id.UserID]*event.MemberEventContent, error) {
// TODO use cache?
fetched, err := br.Bot.StateStore.HasFetchedMembers(ctx, roomID)
if err != nil {
return nil, err
} else if fetched {
return br.Bot.StateStore.GetAllMembers(ctx, roomID)
}
members, err := br.Bot.Members(ctx, roomID)
if err != nil {
return nil, err

View file

@ -535,6 +535,10 @@ func (as *ASIntent) DeleteRoom(ctx context.Context, roomID id.RoomID, puppetsOnl
if err != nil {
zerolog.Ctx(ctx).Err(err).Msg("Failed to leave room while cleaning up portal")
}
err = as.Matrix.StateStore.ClearCachedMembers(ctx, roomID)
if err != nil {
zerolog.Ctx(ctx).Err(err).Msg("Failed to clear cached members while cleaning up portal")
}
return nil
}

View file

@ -1444,6 +1444,11 @@ func (cli *Client) State(ctx context.Context, roomID id.RoomID) (stateMap RoomSt
UpdateStateStore(ctx, cli.StateStore, evt)
}
}
clearErr = cli.StateStore.MarkMembersFetched(ctx, roomID)
if clearErr != nil {
cli.cliOrContextLog(ctx).Warn().Err(clearErr).
Msg("Failed to mark members as fetched after fetching full room state")
}
}
return
}
@ -1840,6 +1845,13 @@ func (cli *Client) Members(ctx context.Context, roomID id.RoomID, req ...ReqMemb
for _, evt := range resp.Chunk {
UpdateStateStore(ctx, cli.StateStore, evt)
}
if extra.NotMembership == "" && extra.Membership == "" {
markErr := cli.StateStore.MarkMembersFetched(ctx, roomID)
if markErr != nil {
cli.cliOrContextLog(ctx).Warn().Err(markErr).
Msg("Failed to mark members as fetched after fetching full member list")
}
}
}
return
}

View file

@ -10,6 +10,7 @@ import (
"context"
"database/sql"
"errors"
"fmt"
"go.mau.fi/util/dbutil"
"golang.org/x/exp/slices"
@ -115,6 +116,18 @@ func (c *ClientStateStore) GetRoomJoinedOrInvitedMembers(ctx context.Context, ro
return dbutil.NewRowIterWithError(rows, dbutil.ScanSingleColumn[id.UserID], err).AsList()
}
func (c *ClientStateStore) HasFetchedMembers(ctx context.Context, roomID id.RoomID) (bool, error) {
return false, fmt.Errorf("not implemented")
}
func (c *ClientStateStore) MarkMembersFetched(ctx context.Context, roomID id.RoomID) error {
return fmt.Errorf("not implemented")
}
func (c *ClientStateStore) GetAllMembers(ctx context.Context, roomID id.RoomID) (map[id.UserID]*event.MemberEventContent, error) {
return nil, fmt.Errorf("not implemented")
}
func (c *ClientStateStore) IsEncrypted(ctx context.Context, roomID id.RoomID) (isEncrypted bool, err error) {
err = c.QueryRow(ctx, isRoomEncryptedQuery, roomID).Scan(&isEncrypted)
if errors.Is(err, sql.ErrNoRows) {

View file

@ -234,9 +234,50 @@ func (store *SQLStateStore) ClearCachedMembers(ctx context.Context, roomID id.Ro
query += fmt.Sprintf(" AND membership IN (%s)", strings.Join(placeholders, ","))
}
_, err := store.Exec(ctx, query, params...)
if err != nil {
return err
}
_, err = store.Exec(ctx, "UPDATE mx_room_state SET members_fetched=false WHERE room_id=$1", roomID)
return err
}
func (store *SQLStateStore) HasFetchedMembers(ctx context.Context, roomID id.RoomID) (fetched bool, err error) {
err = store.QueryRow(ctx, "SELECT members_fetched FROM mx_room_state WHERE room_id=$1", roomID).Scan(&fetched)
if errors.Is(err, sql.ErrNoRows) {
err = nil
}
return
}
func (store *SQLStateStore) MarkMembersFetched(ctx context.Context, roomID id.RoomID) error {
_, err := store.Exec(ctx, `
INSERT INTO mx_room_state (room_id, members_fetched) VALUES ($1, true)
ON CONFLICT (room_id) DO UPDATE SET members_fetched=true
`, roomID)
return err
}
type userAndMembership struct {
UserID id.UserID
event.MemberEventContent
}
func (store *SQLStateStore) GetAllMembers(ctx context.Context, roomID id.RoomID) (map[id.UserID]*event.MemberEventContent, error) {
rows, err := store.Query(ctx, "SELECT user_id, membership, displayname, avatar_url FROM mx_user_profile WHERE room_id=$1", roomID)
if err != nil {
return nil, err
}
output := make(map[id.UserID]*event.MemberEventContent)
err = dbutil.NewRowIterWithError(rows, func(row dbutil.Scannable) (res userAndMembership, err error) {
err = row.Scan(&res.UserID, &res.Membership, &res.Displayname, &res.AvatarURL)
return
}, err).Iter(func(member userAndMembership) (bool, error) {
output[member.UserID] = &member.MemberEventContent
return true, nil
})
return output, err
}
func (store *SQLStateStore) SetEncryptionEvent(ctx context.Context, roomID id.RoomID, content *event.EncryptionEventContent) error {
contentBytes, err := json.Marshal(content)
if err != nil {

View file

@ -1,4 +1,4 @@
-- v0 -> v6 (compatible with v3+): Latest revision
-- v0 -> v7 (compatible with v3+): Latest revision
CREATE TABLE mx_registrations (
user_id TEXT PRIMARY KEY
@ -8,11 +8,11 @@ CREATE TABLE mx_registrations (
CREATE TYPE membership AS ENUM ('join', 'leave', 'invite', 'ban', 'knock');
CREATE TABLE mx_user_profile (
room_id TEXT,
user_id TEXT,
membership membership NOT NULL,
displayname TEXT NOT NULL DEFAULT '',
avatar_url TEXT NOT NULL DEFAULT '',
room_id TEXT,
user_id TEXT,
membership membership NOT NULL,
displayname TEXT NOT NULL DEFAULT '',
avatar_url TEXT NOT NULL DEFAULT '',
name_skeleton bytea,
@ -23,7 +23,8 @@ CREATE INDEX mx_user_profile_membership_idx ON mx_user_profile (room_id, members
CREATE INDEX mx_user_profile_name_skeleton_idx ON mx_user_profile (room_id, name_skeleton);
CREATE TABLE mx_room_state (
room_id TEXT PRIMARY KEY,
power_levels jsonb,
encryption jsonb
room_id TEXT PRIMARY KEY,
power_levels jsonb,
encryption jsonb,
members_fetched BOOLEAN NOT NULL DEFAULT false
);

View file

@ -0,0 +1,2 @@
-- v7 (compatible with v3+): Add flag for whether the full member list has been fetched
ALTER TABLE mx_room_state ADD COLUMN members_fetched BOOLEAN NOT NULL DEFAULT false;

View file

@ -8,6 +8,7 @@ package mautrix
import (
"context"
"maps"
"sync"
"github.com/rs/zerolog"
@ -32,6 +33,10 @@ type StateStore interface {
SetPowerLevels(ctx context.Context, roomID id.RoomID, levels *event.PowerLevelsEventContent) error
GetPowerLevels(ctx context.Context, roomID id.RoomID) (*event.PowerLevelsEventContent, error)
HasFetchedMembers(ctx context.Context, roomID id.RoomID) (bool, error)
MarkMembersFetched(ctx context.Context, roomID id.RoomID) error
GetAllMembers(ctx context.Context, roomID id.RoomID) (map[id.UserID]*event.MemberEventContent, error)
SetEncryptionEvent(ctx context.Context, roomID id.RoomID, content *event.EncryptionEventContent) error
IsEncrypted(ctx context.Context, roomID id.RoomID) (bool, error)
@ -90,10 +95,11 @@ func (cli *Client) StateStoreSyncHandler(ctx context.Context, evt *event.Event)
}
type MemoryStateStore struct {
Registrations map[id.UserID]bool `json:"registrations"`
Members map[id.RoomID]map[id.UserID]*event.MemberEventContent `json:"memberships"`
PowerLevels map[id.RoomID]*event.PowerLevelsEventContent `json:"power_levels"`
Encryption map[id.RoomID]*event.EncryptionEventContent `json:"encryption"`
Registrations map[id.UserID]bool `json:"registrations"`
Members map[id.RoomID]map[id.UserID]*event.MemberEventContent `json:"memberships"`
MembersFetched map[id.RoomID]bool `json:"members_fetched"`
PowerLevels map[id.RoomID]*event.PowerLevelsEventContent `json:"power_levels"`
Encryption map[id.RoomID]*event.EncryptionEventContent `json:"encryption"`
registrationsLock sync.RWMutex
membersLock sync.RWMutex
@ -103,10 +109,11 @@ type MemoryStateStore struct {
func NewMemoryStateStore() StateStore {
return &MemoryStateStore{
Registrations: make(map[id.UserID]bool),
Members: make(map[id.RoomID]map[id.UserID]*event.MemberEventContent),
PowerLevels: make(map[id.RoomID]*event.PowerLevelsEventContent),
Encryption: make(map[id.RoomID]*event.EncryptionEventContent),
Registrations: make(map[id.UserID]bool),
Members: make(map[id.RoomID]map[id.UserID]*event.MemberEventContent),
MembersFetched: make(map[id.RoomID]bool),
PowerLevels: make(map[id.RoomID]*event.PowerLevelsEventContent),
Encryption: make(map[id.RoomID]*event.EncryptionEventContent),
}
}
@ -246,9 +253,29 @@ func (store *MemoryStateStore) ClearCachedMembers(_ context.Context, roomID id.R
}
}
}
store.MembersFetched[roomID] = false
return nil
}
func (store *MemoryStateStore) HasFetchedMembers(ctx context.Context, roomID id.RoomID) (bool, error) {
store.membersLock.RLock()
defer store.membersLock.RUnlock()
return store.MembersFetched[roomID], nil
}
func (store *MemoryStateStore) MarkMembersFetched(ctx context.Context, roomID id.RoomID) error {
store.membersLock.Lock()
defer store.membersLock.Unlock()
store.MembersFetched[roomID] = true
return nil
}
func (store *MemoryStateStore) GetAllMembers(ctx context.Context, roomID id.RoomID) (map[id.UserID]*event.MemberEventContent, error) {
store.membersLock.Lock()
defer store.membersLock.Unlock()
return maps.Clone(store.Members[roomID]), nil
}
func (store *MemoryStateStore) SetPowerLevels(_ context.Context, roomID id.RoomID, levels *event.PowerLevelsEventContent) error {
store.powerLevelsLock.Lock()
store.PowerLevels[roomID] = levels