mirror of
https://github.com/matrix-org/sliding-sync.git
synced 2025-03-10 13:37:11 +00:00
263 lines
8.7 KiB
Go
263 lines
8.7 KiB
Go
package sync3
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/matrix-org/sliding-sync/internal"
|
|
)
|
|
|
|
type OverwriteVal bool
|
|
|
|
var (
|
|
DoNotOverwrite OverwriteVal = false
|
|
Overwrite OverwriteVal = true
|
|
)
|
|
|
|
// ListOp represents the possible operations on a list
|
|
type ListOp uint8
|
|
|
|
var (
|
|
// The room is added to the list
|
|
ListOpAdd ListOp = 1
|
|
// The room is removed from the list
|
|
ListOpDel ListOp = 2
|
|
// The room may change position in the list
|
|
ListOpChange ListOp = 3
|
|
)
|
|
|
|
type RoomListDelta struct {
|
|
ListKey string
|
|
Op ListOp
|
|
}
|
|
|
|
type RoomDelta struct {
|
|
RoomNameChanged bool
|
|
RoomAvatarChanged bool
|
|
JoinCountChanged bool
|
|
InviteCountChanged bool
|
|
NotificationCountChanged bool
|
|
HighlightCountChanged bool
|
|
Lists []RoomListDelta
|
|
}
|
|
|
|
// InternalRequestLists is a list of lists which matches each index position in the request
|
|
// JSON 'lists'. It contains all the internal metadata for rooms and controls access and updatings of said
|
|
// lists.
|
|
type InternalRequestLists struct {
|
|
allRooms map[string]*RoomConnMetadata
|
|
lists map[string]*FilteredSortableRooms
|
|
}
|
|
|
|
func NewInternalRequestLists() *InternalRequestLists {
|
|
return &InternalRequestLists{
|
|
allRooms: make(map[string]*RoomConnMetadata, 10),
|
|
lists: make(map[string]*FilteredSortableRooms),
|
|
}
|
|
}
|
|
|
|
func (s *InternalRequestLists) SetRoom(r RoomConnMetadata) (delta RoomDelta) {
|
|
existing, exists := s.allRooms[r.RoomID]
|
|
if exists {
|
|
if existing.NotificationCount != r.NotificationCount {
|
|
delta.NotificationCountChanged = true
|
|
}
|
|
if existing.HighlightCount != r.HighlightCount {
|
|
delta.HighlightCountChanged = true
|
|
}
|
|
delta.InviteCountChanged = !existing.SameInviteCount(&r.RoomMetadata)
|
|
delta.JoinCountChanged = !existing.SameJoinCount(&r.RoomMetadata)
|
|
delta.RoomNameChanged = !existing.SameRoomName(&r.RoomMetadata)
|
|
if delta.RoomNameChanged {
|
|
// update the canonical name to allow room name sorting to continue to work
|
|
roomName, _ := internal.CalculateRoomName(&r.RoomMetadata, 5)
|
|
r.CanonicalisedName = strings.ToLower(
|
|
strings.Trim(roomName, "#!():_@"),
|
|
)
|
|
} else {
|
|
// XXX: during TestConnectionTimeoutNotReset there is some situation where
|
|
// r.CanonicalisedName is the empty string. Looking at the SetRoom
|
|
// call in connstate_live.go, this is because the UserRoomMetadata on
|
|
// the RoomUpdate has an empty CanonicalisedName. Either
|
|
// a) that is expected, in which case we should _always_ write to
|
|
// r.CanonicalisedName here; or
|
|
// b) that is not expected, in which case... erm, I don't know what
|
|
// to conclude.
|
|
r.CanonicalisedName = existing.CanonicalisedName
|
|
}
|
|
delta.RoomAvatarChanged = !existing.SameRoomAvatar(&r)
|
|
if delta.RoomAvatarChanged {
|
|
r.ResolvedAvatarURL = internal.CalculateAvatar(&r.RoomMetadata, r.IsDM)
|
|
}
|
|
|
|
// Interpret the timestamp map on r as the changes we should apply atop the
|
|
// existing timestamps.
|
|
newTimestamps := r.LastInterestedEventTimestamps
|
|
r.LastInterestedEventTimestamps = make(map[string]uint64, len(s.lists))
|
|
for listKey := range s.lists {
|
|
newTs, bump := newTimestamps[listKey]
|
|
if bump {
|
|
r.LastInterestedEventTimestamps[listKey] = newTs
|
|
} else {
|
|
prevTs, hadPreviousTs := existing.LastInterestedEventTimestamps[listKey]
|
|
if hadPreviousTs {
|
|
r.LastInterestedEventTimestamps[listKey] = prevTs
|
|
} else {
|
|
// This can happen if the listKey is brand-new in this request.
|
|
r.LastInterestedEventTimestamps[listKey] = existing.LastMessageTimestamp
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// set the canonical name to allow room name sorting to work
|
|
roomName, _ := internal.CalculateRoomName(&r.RoomMetadata, 5)
|
|
r.CanonicalisedName = strings.ToLower(
|
|
strings.Trim(roomName, "#!():_@"),
|
|
)
|
|
r.ResolvedAvatarURL = internal.CalculateAvatar(&r.RoomMetadata, r.IsDM)
|
|
// We'll automatically use the LastInterestedEventTimestamps provided by the
|
|
// caller, so that recency sorts work.
|
|
}
|
|
// filter.Include may call on this room ID in the RoomFinder, so make sure it finds it.
|
|
s.allRooms[r.RoomID] = &r
|
|
|
|
for listKey, list := range s.lists {
|
|
_, alreadyExists := list.roomIDToIndex[r.RoomID]
|
|
shouldExist := list.filter.Include(&r, s)
|
|
if shouldExist && r.HasLeft {
|
|
shouldExist = false
|
|
}
|
|
// weird nesting ensures we handle all 4 cases
|
|
if alreadyExists {
|
|
if shouldExist { // could be a change
|
|
delta.Lists = append(delta.Lists, RoomListDelta{
|
|
ListKey: listKey,
|
|
Op: ListOpChange,
|
|
})
|
|
} else { // removal
|
|
delta.Lists = append(delta.Lists, RoomListDelta{
|
|
ListKey: listKey,
|
|
Op: ListOpDel,
|
|
})
|
|
}
|
|
} else {
|
|
if shouldExist { // addition
|
|
delta.Lists = append(delta.Lists, RoomListDelta{
|
|
ListKey: listKey,
|
|
Op: ListOpAdd,
|
|
})
|
|
} // else it doesn't exist and it shouldn't exist, so do nothing e.g room isn't relevant to this list
|
|
}
|
|
}
|
|
return delta
|
|
}
|
|
|
|
// Remove a room from all lists e.g retired an invite, left a room
|
|
func (s *InternalRequestLists) RemoveRoom(roomID string) {
|
|
delete(s.allRooms, roomID)
|
|
// TODO: update lists?
|
|
}
|
|
|
|
func (s *InternalRequestLists) DeleteList(listKey string) {
|
|
delete(s.lists, listKey)
|
|
for _, room := range s.allRooms {
|
|
delete(room.LastInterestedEventTimestamps, listKey)
|
|
}
|
|
}
|
|
|
|
// Returns the underlying RoomConnMetadata object. Returns a shared pointer, not a copy.
|
|
// It is only safe to read this data, never to write.
|
|
func (s *InternalRequestLists) ReadOnlyRoom(roomID string) *RoomConnMetadata {
|
|
return s.allRooms[roomID]
|
|
}
|
|
|
|
// Get returns the sorted list of rooms. Returns a shared pointer, not a copy.
|
|
// It is only safe to read this data, never to write.
|
|
func (s *InternalRequestLists) Get(listKey string) *FilteredSortableRooms {
|
|
return s.lists[listKey]
|
|
}
|
|
|
|
// ListKeys returns a copy of the list keys currently tracked by this
|
|
// InternalRequestLists struct, in no particular order. Outside of test code, you
|
|
// probably don't want to call this---you probably have the set of list keys tracked
|
|
// elsewhere in the application.
|
|
func (s *InternalRequestLists) ListKeys() []string {
|
|
keys := make([]string, len(s.lists))
|
|
for listKey, _ := range s.lists {
|
|
keys = append(keys, listKey)
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// ListsByVisibleRoomIDs builds a map from room IDs to a slice of list names. Keys are
|
|
// all room IDs that are currently visible in at least one sliding window. Values are
|
|
// the names of all lists (in no particular order) in which the given room ID is
|
|
// currently visible. The value slices are nonnil and contain at least one list name
|
|
// (possibly more).
|
|
//
|
|
// The returned map is a copy, i.e. is safe to modify by the caller.
|
|
func (s *InternalRequestLists) ListsByVisibleRoomIDs(muxedReqLists map[string]RequestList) map[string][]string {
|
|
listsByRoomIDs := make(map[string][]string, len(muxedReqLists))
|
|
// Loop over each list, and mark each room in its sliding window as being visible in this list.
|
|
for listKey, reqList := range muxedReqLists {
|
|
sortedRooms := s.lists[listKey].SortableRooms
|
|
if sortedRooms == nil {
|
|
continue
|
|
}
|
|
|
|
// If we've requested all rooms, every room is visible in this list---we don't
|
|
// have to worry about extracting room IDs in the sliding windows' ranges.
|
|
if reqList.SlowGetAllRooms != nil && *reqList.SlowGetAllRooms {
|
|
for _, roomID := range sortedRooms.RoomIDs() {
|
|
listsByRoomIDs[roomID] = append(listsByRoomIDs[roomID], listKey)
|
|
}
|
|
} else {
|
|
subslices := reqList.Ranges.SliceInto(sortedRooms)
|
|
for _, subslice := range subslices {
|
|
sortedRooms = subslice.(*SortableRooms)
|
|
for _, roomID := range sortedRooms.RoomIDs() {
|
|
listsByRoomIDs[roomID] = append(listsByRoomIDs[roomID], listKey)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return listsByRoomIDs
|
|
}
|
|
|
|
// Assign a new list at the given key. If Overwrite, any existing list is replaced. If DoNotOverwrite, the existing
|
|
// list is returned if one exists, else a new list is created. Returns the list and true if the list was overwritten.
|
|
func (s *InternalRequestLists) AssignList(ctx context.Context, listKey string, filters *RequestFilters, sort []string, shouldOverwrite OverwriteVal) (*FilteredSortableRooms, bool) {
|
|
if shouldOverwrite == DoNotOverwrite {
|
|
_, exists := s.lists[listKey]
|
|
if exists {
|
|
return s.lists[listKey], false
|
|
}
|
|
}
|
|
roomIDs := make([]string, len(s.allRooms))
|
|
i := 0
|
|
for roomID := range s.allRooms {
|
|
roomIDs[i] = roomID
|
|
i++
|
|
}
|
|
|
|
roomList := NewFilteredSortableRooms(s, listKey, roomIDs, filters)
|
|
if sort != nil {
|
|
err := roomList.Sort(sort)
|
|
if err != nil {
|
|
logger.Err(err).Strs("sort_by", sort).Msg("failed to sort")
|
|
internal.GetSentryHubFromContextOrDefault(ctx).CaptureException(err)
|
|
}
|
|
}
|
|
s.lists[listKey] = roomList
|
|
return roomList, true
|
|
}
|
|
|
|
// Count returns the count of total rooms in this list
|
|
func (s *InternalRequestLists) Count(listKey string) int {
|
|
return int(s.lists[listKey].Len())
|
|
}
|
|
|
|
func (s *InternalRequestLists) Len() int {
|
|
return len(s.lists)
|
|
}
|