mirror of
https://github.com/matrix-org/sliding-sync.git
synced 2025-03-10 13:37:11 +00:00
247 lines
6.4 KiB
Go
247 lines
6.4 KiB
Go
package sync3
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/matrix-org/sliding-sync/internal"
|
|
)
|
|
|
|
type RoomFinder interface {
|
|
ReadOnlyRoom(roomID string) *RoomConnMetadata
|
|
}
|
|
|
|
// SortableRooms represents a list of rooms which can be sorted and updated. Maintains mappings of
|
|
// room IDs to current index positions after sorting.
|
|
type SortableRooms struct {
|
|
finder RoomFinder
|
|
listKey string
|
|
roomIDs []string
|
|
roomIDToIndex map[string]int // room_id -> index in rooms
|
|
}
|
|
|
|
func NewSortableRooms(finder RoomFinder, listKey string, rooms []string) *SortableRooms {
|
|
return &SortableRooms{
|
|
roomIDs: rooms,
|
|
finder: finder,
|
|
listKey: listKey,
|
|
roomIDToIndex: make(map[string]int),
|
|
}
|
|
}
|
|
|
|
func (s *SortableRooms) IndexOf(roomID string) (int, bool) {
|
|
index, ok := s.roomIDToIndex[roomID]
|
|
return index, ok
|
|
}
|
|
|
|
func (s *SortableRooms) RoomIDs() []string {
|
|
roomIDs := make([]string, len(s.roomIDs))
|
|
for i := range s.roomIDs {
|
|
roomIDs[i] = s.roomIDs[i]
|
|
}
|
|
return roomIDs
|
|
}
|
|
|
|
// Add a room to the list. Returns true if the room was added.
|
|
func (s *SortableRooms) Add(roomID string) bool {
|
|
_, exists := s.roomIDToIndex[roomID]
|
|
if exists {
|
|
return false
|
|
}
|
|
s.roomIDs = append(s.roomIDs, roomID)
|
|
s.roomIDToIndex[roomID] = len(s.roomIDs) - 1
|
|
return true
|
|
}
|
|
|
|
func (s *SortableRooms) Get(index int) string {
|
|
// TODO: find a way to plumb a context into this assert
|
|
internal.Assert(fmt.Sprintf("index is within len(rooms) %v < %v", index, len(s.roomIDs)), index < len(s.roomIDs))
|
|
return s.roomIDs[index]
|
|
}
|
|
|
|
func (s *SortableRooms) Remove(roomID string) int {
|
|
index, ok := s.roomIDToIndex[roomID]
|
|
if !ok {
|
|
return -1
|
|
}
|
|
delete(s.roomIDToIndex, roomID)
|
|
// splice out index
|
|
s.roomIDs = append(s.roomIDs[:index], s.roomIDs[index+1:]...)
|
|
// re-update the map
|
|
for i := index; i < len(s.roomIDs); i++ {
|
|
s.roomIDToIndex[s.roomIDs[i]] = i
|
|
}
|
|
return index
|
|
}
|
|
|
|
func (s *SortableRooms) Len() int64 {
|
|
return int64(len(s.roomIDs))
|
|
}
|
|
func (s *SortableRooms) Subslice(i, j int64) Subslicer {
|
|
// TODO: find a way to plumb a context.Context through to this assert
|
|
internal.Assert("i < j and are within len(rooms)", i < j && i < int64(len(s.roomIDs)) && j <= int64(len(s.roomIDs)))
|
|
return &SortableRooms{
|
|
roomIDs: s.roomIDs[i:j],
|
|
roomIDToIndex: s.roomIDToIndex,
|
|
}
|
|
}
|
|
|
|
func (s *SortableRooms) Sort(sortBy []string) error {
|
|
// TODO: find a way to plumb a context into this assert
|
|
internal.Assert("sortBy is not empty", len(sortBy) != 0)
|
|
comparators := []func(i, j int) int{}
|
|
for _, sort := range sortBy {
|
|
switch sort {
|
|
case SortByHighlightCount:
|
|
comparators = append(comparators, s.comparatorSortByHighlightCount)
|
|
case SortByNotificationCount:
|
|
comparators = append(comparators, s.comparatorSortByNotificationCount)
|
|
case SortByName:
|
|
comparators = append(comparators, s.comparatorSortByName)
|
|
case SortByRecency:
|
|
comparators = append(comparators, s.comparatorSortByRecency)
|
|
case SortByNotificationLevel:
|
|
comparators = append(comparators, s.comparatorSortByNotificationLevel)
|
|
default:
|
|
return fmt.Errorf("unknown sort order: %s", sort)
|
|
}
|
|
}
|
|
sort.SliceStable(s.roomIDs, func(i, j int) bool {
|
|
for _, fn := range comparators {
|
|
val := fn(i, j)
|
|
if val == 1 {
|
|
return true
|
|
} else if val == -1 {
|
|
return false
|
|
}
|
|
// continue to next comparator as these are equal
|
|
}
|
|
// the two items are identical
|
|
return false
|
|
})
|
|
for i := range s.roomIDs {
|
|
s.roomIDToIndex[s.roomIDs[i]] = i
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Comparator functions: -1 = false, +1 = true, 0 = match
|
|
|
|
func (s *SortableRooms) resolveRooms(i, j int) (ri, rj *RoomConnMetadata) {
|
|
ri = s.finder.ReadOnlyRoom(s.roomIDs[i])
|
|
rj = s.finder.ReadOnlyRoom(s.roomIDs[j])
|
|
return
|
|
}
|
|
|
|
func (s *SortableRooms) comparatorSortByName(i, j int) int {
|
|
ri, rj := s.resolveRooms(i, j)
|
|
if ri.CanonicalisedName == rj.CanonicalisedName {
|
|
return 0
|
|
}
|
|
if ri.CanonicalisedName < rj.CanonicalisedName {
|
|
return 1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (s *SortableRooms) comparatorSortByRecency(i, j int) int {
|
|
ri, rj := s.resolveRooms(i, j)
|
|
tsRi := ri.GetLastInterestedEventTimestamp(s.listKey)
|
|
tsRj := rj.GetLastInterestedEventTimestamp(s.listKey)
|
|
if tsRi == tsRj {
|
|
return 0
|
|
}
|
|
if tsRi > tsRj {
|
|
return 1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (s *SortableRooms) comparatorSortByHighlightCount(i, j int) int {
|
|
ri, rj := s.resolveRooms(i, j)
|
|
if ri.HighlightCount == rj.HighlightCount {
|
|
return 0
|
|
}
|
|
if ri.HighlightCount > rj.HighlightCount {
|
|
return 1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (s *SortableRooms) comparatorSortByNotificationLevel(i, j int) int {
|
|
ri, rj := s.resolveRooms(i, j)
|
|
// highlight rooms come first
|
|
if ri.HighlightCount > 0 && rj.HighlightCount > 0 {
|
|
return 0
|
|
}
|
|
if ri.HighlightCount > 0 {
|
|
return 1
|
|
} else if rj.HighlightCount > 0 {
|
|
return -1
|
|
}
|
|
|
|
// then notification count
|
|
if ri.NotificationCount > 0 && rj.NotificationCount > 0 {
|
|
// when we are comparing rooms with notif counts, sort encrypted rooms above unencrypted rooms
|
|
// as the client needs to calculate highlight counts (so it's possible that notif counts are
|
|
// actually highlight counts!) - this is the "Lite" description in MSC3575
|
|
if ri.Encrypted && !rj.Encrypted {
|
|
return 1
|
|
} else if rj.Encrypted && !ri.Encrypted {
|
|
return -1
|
|
}
|
|
return 0
|
|
}
|
|
if ri.NotificationCount > 0 {
|
|
return 1
|
|
} else if rj.NotificationCount > 0 {
|
|
return -1
|
|
}
|
|
// no highlight or notifs get grouped together
|
|
return 0
|
|
}
|
|
|
|
func (s *SortableRooms) comparatorSortByNotificationCount(i, j int) int {
|
|
ri, rj := s.resolveRooms(i, j)
|
|
if ri.NotificationCount == rj.NotificationCount {
|
|
return 0
|
|
}
|
|
if ri.NotificationCount > rj.NotificationCount {
|
|
return 1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// FilteredSortableRooms is SortableRooms but where rooms are filtered before being added to the list.
|
|
// Updates to room metadata may result in rooms being added/removed.
|
|
type FilteredSortableRooms struct {
|
|
*SortableRooms
|
|
filter *RequestFilters
|
|
}
|
|
|
|
func NewFilteredSortableRooms(finder RoomFinder, listKey string, roomIDs []string, filter *RequestFilters) *FilteredSortableRooms {
|
|
var filteredRooms []string
|
|
if filter == nil {
|
|
filter = &RequestFilters{}
|
|
}
|
|
for _, roomID := range roomIDs {
|
|
r := finder.ReadOnlyRoom(roomID)
|
|
if filter.Include(r, finder) {
|
|
filteredRooms = append(filteredRooms, roomID)
|
|
}
|
|
}
|
|
return &FilteredSortableRooms{
|
|
SortableRooms: NewSortableRooms(finder, listKey, filteredRooms),
|
|
filter: filter,
|
|
}
|
|
}
|
|
|
|
func (f *FilteredSortableRooms) Add(roomID string) bool {
|
|
r := f.finder.ReadOnlyRoom(roomID)
|
|
if !f.filter.Include(r, f.finder) {
|
|
return false
|
|
}
|
|
return f.SortableRooms.Add(roomID)
|
|
}
|