From 442fdac4d5b949e556e94b0be53f2208709e8bf3 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sat, 22 Feb 2020 00:03:57 +0200 Subject: [PATCH] Enable lazy loading of members --- go.mod | 4 +-- go.sum | 4 +++ interface/matrix.go | 1 + matrix/matrix.go | 15 +++++++++ matrix/rooms/room.go | 73 ++++++++++++++++++-------------------------- matrix/sync.go | 7 +++++ ui/member-list.go | 38 ++++++++++++++++++++--- ui/view-main.go | 11 +++++++ 8 files changed, 104 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index 78cea34..95bfaca 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( golang.org/x/net v0.0.0-20200202094626-16171245cfb2 gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 gopkg.in/yaml.v2 v2.2.8 - maunium.net/go/mautrix v0.1.0-alpha.3.0.20200220001222-8dc3dd5d538d - maunium.net/go/mauview v0.0.0-20200220201003-92b19f8819b4 + maunium.net/go/mautrix v0.1.0-alpha.3.0.20200221220303-b441ba9359cf + maunium.net/go/mauview v0.0.0-20200220222850-39f1414676d9 maunium.net/go/tcell v1.1.2-0.20200218183045-87c4a25c5b09 ) diff --git a/go.sum b/go.sum index a13fd38..d7ca8f4 100644 --- a/go.sum +++ b/go.sum @@ -82,6 +82,8 @@ maunium.net/go/mautrix v0.1.0-alpha.3.0.20200219230859-de66c34ea5bc h1:1iMzqdMF4 maunium.net/go/mautrix v0.1.0-alpha.3.0.20200219230859-de66c34ea5bc/go.mod h1:g10T1fh2Q2HkJWycVs93eBXdWpqD67f1YVQhNxdIDr4= maunium.net/go/mautrix v0.1.0-alpha.3.0.20200220001222-8dc3dd5d538d h1:doAHXnYCIgNy4qAgReZRRb3EaWR4D0w+Zs2y959Z8Uk= maunium.net/go/mautrix v0.1.0-alpha.3.0.20200220001222-8dc3dd5d538d/go.mod h1:g10T1fh2Q2HkJWycVs93eBXdWpqD67f1YVQhNxdIDr4= +maunium.net/go/mautrix v0.1.0-alpha.3.0.20200221220303-b441ba9359cf h1:ojEsISqRLmM9PRIJiVDTRHNmj5IZoXn3E64S+2uTJNw= +maunium.net/go/mautrix v0.1.0-alpha.3.0.20200221220303-b441ba9359cf/go.mod h1:g10T1fh2Q2HkJWycVs93eBXdWpqD67f1YVQhNxdIDr4= maunium.net/go/mauview v0.0.0-20200218183549-88ecb1321176 h1:KoTm7ASEzFIZ1SvPWuWYzpkeA+wiR1fuUu4l7TCHcE0= maunium.net/go/mauview v0.0.0-20200218183549-88ecb1321176/go.mod h1:jwg3Ow7akzsCX3q38pZAfmEC5gGN8gXwMyyjy/yZVMg= maunium.net/go/mauview v0.0.0-20200218231215-04d01c601d5b h1:Bfov5IkJQpkqDexiFioHIZpx4XL7AILDA1GwLVdqtBw= @@ -92,5 +94,7 @@ maunium.net/go/mauview v0.0.0-20200219222453-b984e20438e6 h1:yYs5rsnDQrZie4eeWlc maunium.net/go/mauview v0.0.0-20200219222453-b984e20438e6/go.mod h1:jwg3Ow7akzsCX3q38pZAfmEC5gGN8gXwMyyjy/yZVMg= maunium.net/go/mauview v0.0.0-20200220201003-92b19f8819b4 h1:60G4iPYhO5Z4qkcniM+xPBeXNmuyD1tqXzj8ryeEdWY= maunium.net/go/mauview v0.0.0-20200220201003-92b19f8819b4/go.mod h1:jwg3Ow7akzsCX3q38pZAfmEC5gGN8gXwMyyjy/yZVMg= +maunium.net/go/mauview v0.0.0-20200220222850-39f1414676d9 h1:xwuAYvp2fdCNJHe4bj04BjKFRAX390gZUK21x6NoePM= +maunium.net/go/mauview v0.0.0-20200220222850-39f1414676d9/go.mod h1:jwg3Ow7akzsCX3q38pZAfmEC5gGN8gXwMyyjy/yZVMg= maunium.net/go/tcell v1.1.2-0.20200218183045-87c4a25c5b09 h1:hu+R+0nodoZPS19WGyYiw/d63+/NQS/R3Duw3d9HqAU= maunium.net/go/tcell v1.1.2-0.20200218183045-87c4a25c5b09/go.mod h1:Ru7KmI5AU7xHUx6hGltgJvknrS+8jlGGMKK15pZuc9k= diff --git a/interface/matrix.go b/interface/matrix.go index af20509..0b1278d 100644 --- a/interface/matrix.go +++ b/interface/matrix.go @@ -43,6 +43,7 @@ type MatrixContainer interface { LeaveRoom(roomID string) error CreateRoom(req *mautrix.ReqCreateRoom) (*rooms.Room, error) + FetchMembers(room *rooms.Room) error GetHistory(room *rooms.Room, limit int) ([]*event.Event, error) GetEvent(room *rooms.Room, eventID string) (*event.Event, error) GetRoom(roomID string) *rooms.Room diff --git a/matrix/matrix.go b/matrix/matrix.go index f0009bc..d3edde5 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -834,6 +834,18 @@ func (c *Container) LeaveRoom(roomID string) error { return nil } +func (c *Container) FetchMembers(room *rooms.Room) error { + members, err := c.client.Members(room.ID, mautrix.ReqMembers{At: room.LastPrevBatch}) + if err != nil { + return err + } + for _, evt := range members.Chunk { + room.UpdateState(evt) + } + room.MembersFetched = true + return nil +} + // GetHistory fetches room history. func (c *Container) GetHistory(room *rooms.Room, limit int) ([]*event.Event, error) { events, err := c.history.Load(room, limit) @@ -849,6 +861,9 @@ func (c *Container) GetHistory(room *rooms.Room, limit int) ([]*event.Event, err return nil, err } debug.Printf("Loaded %d events for %s from server from %s to %s", len(resp.Chunk), room.ID, resp.Start, resp.End) + for _, evt := range resp.State { + room.UpdateState(evt) + } room.PrevBatch = resp.End c.config.Rooms.Put(room) if len(resp.Chunk) == 0 { diff --git a/matrix/rooms/room.go b/matrix/rooms/room.go index 4632730..9081ba5 100644 --- a/matrix/rooms/room.go +++ b/matrix/rooms/room.go @@ -21,7 +21,6 @@ import ( "encoding/gob" "fmt" "os" - "sort" "time" sync "github.com/sasha-s/go-deadlock" @@ -41,7 +40,6 @@ type RoomNameSource int const ( UnknownRoomName RoomNameSource = iota MemberRoomName - AliasRoomName CanonicalAliasRoomName ExplicitRoomName ) @@ -71,6 +69,8 @@ type Room struct { // The first batch of events that has been fetched for this room. // Used for fetching additional history. PrevBatch string + // The last_batch field from the most recent sync. Used for fetching member lists. + LastPrevBatch string // The MXID of the user whose session this room was created for. SessionUserID string SessionMember *mautrix.Member @@ -88,6 +88,10 @@ type Room struct { // Timestamp of previously received actual message. LastReceivedMessage time.Time + // The lazy loading summary for this room. + Summary mautrix.LazyLoadSummary + // Whether or not the members for this room have been fetched from the server. + MembersFetched bool // Room state cache. state map[mautrix.EventType]map[string]*mautrix.Event // MXID -> Member cache calculated from membership events. @@ -106,8 +110,6 @@ type Room struct { topicCache string // The canonical alias of the room. Directly fetched from the m.room.canonical_alias state event. CanonicalAliasCache string - // The list of aliases. Directly fetched from the m.room.aliases state event. - aliasesCache []string // Whether or not the room has been tombstoned. replacedCache bool // The room ID that replaced this room. @@ -199,13 +201,13 @@ func (room *Room) Unload() bool { debug.Print("Unloading", room.ID) room.Save() room.state = nil - room.aliasesCache = nil room.topicCache = "" room.CanonicalAliasCache = "" room.firstMemberCache = nil room.secondMemberCache = nil room.memberCache = nil room.exMemberCache = nil + room.replacedByCache = nil if room.postUnload != nil { room.postUnload() } @@ -343,6 +345,21 @@ func (room *Room) Tags() []RoomTag { return room.RawTags } +func (room *Room) UpdateSummary(summary mautrix.LazyLoadSummary) { + if summary.JoinedMemberCount != nil { + room.Summary.JoinedMemberCount = summary.JoinedMemberCount + } + if summary.InvitedMemberCount != nil { + room.Summary.InvitedMemberCount = summary.InvitedMemberCount + } + if summary.Heroes != nil { + room.Summary.Heroes = summary.Heroes + } + if room.nameCacheSource <= MemberRoomName { + room.NameCache = "" + } +} + // UpdateState updates the room's current state with the given Event. This will clobber events based // on the type/state_key combination. func (room *Room) UpdateState(event *mautrix.Event) { @@ -367,11 +384,6 @@ func (room *Room) UpdateState(event *mautrix.Event) { room.nameCacheSource = CanonicalAliasRoomName } room.CanonicalAliasCache = event.Content.Alias - case mautrix.StateAliases: - if room.nameCacheSource <= AliasRoomName { - room.NameCache = "" - } - room.aliasesCache = nil case mautrix.StateMember: if room.nameCacheSource <= MemberRoomName { room.NameCache = "" @@ -395,7 +407,7 @@ func (room *Room) updateMemberState(event *mautrix.Event) { } if room.memberCache != nil { member := room.eventToMember(userID, &event.Content) - if event.Content.Membership.IsInviteOrJoin() { + if member.Membership.IsInviteOrJoin() { existingMember, ok := room.memberCache[userID] if ok { *existingMember = *member @@ -458,20 +470,6 @@ func (room *Room) GetCanonicalAlias() string { return room.CanonicalAliasCache } -// GetAliases returns the list of aliases that point to this room. -func (room *Room) GetAliases() []string { - if room.aliasesCache == nil { - room.lock.RLock() - aliasEvents := room.getStateEvents(mautrix.StateAliases) - room.aliasesCache = []string{} - for _, event := range aliasEvents { - room.aliasesCache = append(room.aliasesCache, event.Content.Aliases...) - } - room.lock.RUnlock() - } - return room.aliasesCache -} - // updateNameFromNameEvent updates the room display name to be the name set in the name event. func (room *Room) updateNameFromNameEvent() { nameEvt := room.GetStateEvent(mautrix.StateRoomName, "") @@ -480,19 +478,6 @@ func (room *Room) updateNameFromNameEvent() { } } -// updateNameFromAliases updates the room display name to be the first room alias it finds. -// -// Deprecated: the Client-Server API recommends against using non-canonical aliases as display name. -func (room *Room) updateNameFromAliases() { - // TODO the spec says clients should not use m.room.aliases for room names. - // However, Riot also uses m.room.aliases, so this is here now. - aliases := room.GetAliases() - if len(aliases) > 0 { - sort.Sort(sort.StringSlice(aliases)) - room.NameCache = aliases[0] - } -} - // updateNameFromMembers updates the room display name based on the members in this room. // // The room name depends on the number of users: @@ -533,10 +518,6 @@ func (room *Room) updateNameCache() { room.NameCache = room.GetCanonicalAlias() room.nameCacheSource = CanonicalAliasRoomName } - if len(room.NameCache) == 0 { - room.updateNameFromAliases() - room.nameCacheSource = AliasRoomName - } if len(room.NameCache) == 0 { room.updateNameFromMembers() room.nameCacheSource = MemberRoomName @@ -616,10 +597,16 @@ func (room *Room) createMemberCache() map[string]*mautrix.Member { } } } + if len(room.Summary.Heroes) > 1 { + room.firstMemberCache, _ = cache[room.Summary.Heroes[0]] + } + if len(room.Summary.Heroes) > 2 { + room.secondMemberCache, _ = cache[room.Summary.Heroes[1]] + } room.lock.RUnlock() room.lock.Lock() room.memberCache = cache - room.exMemberCache = cache + room.exMemberCache = exCache room.lock.Unlock() return cache } diff --git a/matrix/sync.go b/matrix/sync.go index 622a235..564848b 100644 --- a/matrix/sync.go +++ b/matrix/sync.go @@ -112,6 +112,7 @@ func (s *GomuksSyncer) ProcessResponse(res *mautrix.RespSync, since string) (err for roomID, roomData := range res.Rooms.Join { room := s.Session.GetRoom(roomID) + room.UpdateSummary(roomData.Summary) s.processSyncEvents(room, roomData.State.Events, EventSourceJoin|EventSourceState) s.processSyncEvents(room, roomData.Timeline.Events, EventSourceJoin|EventSourceTimeline) s.processSyncEvents(room, roomData.Ephemeral.Events, EventSourceJoin|EventSourceEphemeral) @@ -120,22 +121,26 @@ func (s *GomuksSyncer) ProcessResponse(res *mautrix.RespSync, since string) (err if len(room.PrevBatch) == 0 { room.PrevBatch = roomData.Timeline.PrevBatch } + room.LastPrevBatch = roomData.Timeline.PrevBatch } for roomID, roomData := range res.Rooms.Invite { room := s.Session.GetRoom(roomID) + room.UpdateSummary(roomData.Summary) s.processSyncEvents(room, roomData.State.Events, EventSourceInvite|EventSourceState) } for roomID, roomData := range res.Rooms.Leave { room := s.Session.GetRoom(roomID) room.HasLeft = true + room.UpdateSummary(roomData.Summary) s.processSyncEvents(room, roomData.State.Events, EventSourceLeave|EventSourceState) s.processSyncEvents(room, roomData.Timeline.Events, EventSourceLeave|EventSourceTimeline) if len(room.PrevBatch) == 0 { room.PrevBatch = roomData.Timeline.PrevBatch } + room.LastPrevBatch = roomData.Timeline.PrevBatch } if since == "" && s.InitDoneCallback != nil { @@ -207,6 +212,7 @@ func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage { Room: mautrix.RoomFilter{ IncludeLeave: false, State: mautrix.FilterPart{ + LazyLoadMembers: true, Types: []string{ "m.room.member", "m.room.name", @@ -218,6 +224,7 @@ func (s *GomuksSyncer) GetFilterJSON(userID string) json.RawMessage { }, }, Timeline: mautrix.FilterPart{ + LazyLoadMembers: true, Types: []string{ "m.room.message", "m.room.redaction", diff --git a/ui/member-list.go b/ui/member-list.go index a96436d..b607a32 100644 --- a/ui/member-list.go +++ b/ui/member-list.go @@ -17,6 +17,7 @@ package ui import ( + "math" "sort" "strings" @@ -40,6 +41,7 @@ func NewMemberList() *MemberList { type memberListItem struct { mautrix.Member PowerLevel int + Sigil rune UserID string Color tcell.Color } @@ -64,11 +66,35 @@ func (rml roomMemberList) Swap(i, j int) { func (ml *MemberList) Update(data map[string]*mautrix.Member, levels *mautrix.PowerLevels) *MemberList { ml.list = make(roomMemberList, len(data)) i := 0 + highestLevel := math.MinInt32 + count := 0 + for _, level := range levels.Users { + if level > highestLevel { + highestLevel = level + count = 1 + } else if level == highestLevel { + count++ + } + } for userID, member := range data { + level := levels.GetUserLevel(userID) + sigil := ' ' + if level == highestLevel && count == 1 { + sigil = '~' + } else if level > levels.StateDefault() { + sigil = '&' + } else if level >= levels.Ban() { + sigil = '@' + } else if level >= levels.Kick() || level >= levels.Redact() { + sigil = '%' + } else if level > levels.UsersDefault { + sigil = '+' + } ml.list[i] = &memberListItem{ Member: *member, UserID: userID, - PowerLevel: levels.GetUserLevel(userID), + PowerLevel: level, + Sigil: sigil, Color: widget.GetHashColor(userID), } i++ @@ -79,17 +105,21 @@ func (ml *MemberList) Update(data map[string]*mautrix.Member, levels *mautrix.Po func (ml *MemberList) Draw(screen mauview.Screen) { width, _ := screen.Size() + sigilStyle := tcell.StyleDefault.Background(tcell.ColorGreen).Foreground(tcell.ColorWhite) for y, member := range ml.list { + if member.Sigil != ' ' { + screen.SetCell(0, y, sigilStyle, member.Sigil) + } if member.Membership == "invite" { - widget.WriteLineSimpleColor(screen, member.Displayname, 1, y, member.Color) - screen.SetCell(0, y, tcell.StyleDefault, '(') + screen.SetCell(1, y, tcell.StyleDefault, '(') if sw := runewidth.StringWidth(member.Displayname); sw < width-1 { screen.SetCell(sw+1, y, tcell.StyleDefault, ')') } else { screen.SetCell(width-1, y, tcell.StyleDefault, ')') } + widget.WriteLineSimpleColor(screen, member.Displayname, 2, y, member.Color) } else { - widget.WriteLineSimpleColor(screen, member.Displayname, 0, y, member.Color) + widget.WriteLineSimpleColor(screen, member.Displayname, 1, y, member.Color) } } } diff --git a/ui/view-main.go b/ui/view-main.go index 507333a..30fa982 100644 --- a/ui/view-main.go +++ b/ui/view-main.go @@ -276,6 +276,17 @@ func (view *MainView) switchRoom(tag string, room *rooms.Room, lock bool) { msgView.initialHistoryLoaded = true go view.LoadHistory(room.ID) } + if !room.MembersFetched { + go func() { + err := view.matrix.FetchMembers(room) + if err != nil { + debug.Print("Error fetching members:", err) + return + } + roomView.UpdateUserList() + view.parent.Render() + }() + } } func (view *MainView) addRoomPage(room *rooms.Room) *RoomView {