diff --git a/pkg/hicli/database/room.go b/pkg/hicli/database/room.go index 234f00f..a27de72 100644 --- a/pkg/hicli/database/room.go +++ b/pkg/hicli/database/room.go @@ -27,6 +27,7 @@ const ( FROM room ` getRoomsBySortingTimestampQuery = getRoomBaseQuery + `WHERE sorting_timestamp < $1 AND sorting_timestamp > 0 ORDER BY sorting_timestamp DESC LIMIT $2` + getRoomsByTypeQuery = getRoomBaseQuery + `WHERE room_type = $1` getRoomByIDQuery = getRoomBaseQuery + `WHERE room_id = $1` ensureRoomExistsQuery = ` INSERT INTO room (room_id) VALUES ($1) @@ -96,6 +97,10 @@ func (rq *RoomQuery) GetBySortTS(ctx context.Context, maxTS time.Time, limit int return rq.QueryMany(ctx, getRoomsBySortingTimestampQuery, maxTS.UnixMilli(), limit) } +func (rq *RoomQuery) GetAllSpaces(ctx context.Context) ([]*Room, error) { + return rq.QueryMany(ctx, getRoomsByTypeQuery, event.RoomTypeSpace) +} + func (rq *RoomQuery) Upsert(ctx context.Context, room *Room) error { return rq.Exec(ctx, upsertRoomFromSyncQuery, room.sqlVariables()...) } diff --git a/pkg/hicli/database/space.go b/pkg/hicli/database/space.go index 1139c69..b0b86ef 100644 --- a/pkg/hicli/database/space.go +++ b/pkg/hicli/database/space.go @@ -26,13 +26,18 @@ const ( getTopLevelSpaces = ` SELECT space_id FROM (SELECT DISTINCT(space_id) FROM space_edge) outeredge + LEFT JOIN room_account_data ON + room_account_data.user_id = $1 + AND room_account_data.room_id = outeredge.space_id + AND room_account_data.type = 'org.matrix.msc3230.space_order' WHERE NOT EXISTS( SELECT 1 FROM space_edge inneredge INNER JOIN room ON inneredge.space_id = room.room_id WHERE inneredge.child_id = outeredge.space_id AND (inneredge.child_event_rowid IS NOT NULL OR inneredge.parent_validated) - ) + ) AND EXISTS(SELECT 1 FROM room WHERE room_id = space_id AND room_type = 'm.space') + ORDER BY room_account_data.content->>'$.order' NULLS LAST, space_id ` revalidateAllParents = ` UPDATE space_edge @@ -196,6 +201,12 @@ func (seq *SpaceEdgeQuery) GetAll(ctx context.Context, spaceID id.RoomID) (map[i return edges, err } +var roomIDScanner = dbutil.ConvertRowFn[id.RoomID](dbutil.ScanSingleColumn[id.RoomID]) + +func (seq *SpaceEdgeQuery) GetTopLevelIDs(ctx context.Context, userID id.UserID) ([]id.RoomID, error) { + return roomIDScanner.NewRowIter(seq.GetDB().Query(ctx, getTopLevelSpaces, userID)).AsList() +} + type SpaceEdge struct { SpaceID id.RoomID `json:"space_id,omitempty"` ChildID id.RoomID `json:"child_id"` diff --git a/pkg/hicli/events.go b/pkg/hicli/events.go index c15c8b1..e45a4e8 100644 --- a/pkg/hicli/events.go +++ b/pkg/hicli/events.go @@ -31,13 +31,14 @@ type SyncNotification struct { } type SyncComplete struct { - Since *string `json:"since,omitempty"` - ClearState bool `json:"clear_state,omitempty"` - AccountData map[event.Type]*database.AccountData `json:"account_data"` - Rooms map[id.RoomID]*SyncRoom `json:"rooms"` - LeftRooms []id.RoomID `json:"left_rooms"` - InvitedRooms []*database.InvitedRoom `json:"invited_rooms"` - SpaceEdges map[id.RoomID][]*database.SpaceEdge `json:"space_edges"` + Since *string `json:"since,omitempty"` + ClearState bool `json:"clear_state,omitempty"` + AccountData map[event.Type]*database.AccountData `json:"account_data"` + Rooms map[id.RoomID]*SyncRoom `json:"rooms"` + LeftRooms []id.RoomID `json:"left_rooms"` + InvitedRooms []*database.InvitedRoom `json:"invited_rooms"` + SpaceEdges map[id.RoomID][]*database.SpaceEdge `json:"space_edges"` + TopLevelSpaces []id.RoomID `json:"top_level_spaces"` } func (c *SyncComplete) IsEmpty() bool { diff --git a/pkg/hicli/init.go b/pkg/hicli/init.go index 3c2b40f..5b08de5 100644 --- a/pkg/hicli/init.go +++ b/pkg/hicli/init.go @@ -66,6 +66,49 @@ func (h *HiClient) getInitialSyncRoom(ctx context.Context, room *database.Room) func (h *HiClient) GetInitialSync(ctx context.Context, batchSize int) iter.Seq[*SyncComplete] { return func(yield func(*SyncComplete) bool) { maxTS := time.Now().Add(1 * time.Hour) + { + spaces, err := h.DB.Room.GetAllSpaces(ctx) + if err != nil { + if ctx.Err() == nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to get initial spaces to send to client") + } + return + } + payload := SyncComplete{ + Rooms: make(map[id.RoomID]*SyncRoom, len(spaces)), + } + for _, room := range spaces { + payload.Rooms[room.ID] = h.getInitialSyncRoom(ctx, room) + if ctx.Err() != nil { + return + } + } + payload.TopLevelSpaces, err = h.DB.SpaceEdge.GetTopLevelIDs(ctx, h.Account.UserID) + if err != nil { + if ctx.Err() == nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to get top-level space IDs to send to client") + } + return + } + payload.SpaceEdges, err = h.DB.SpaceEdge.GetAll(ctx, "") + if err != nil { + if ctx.Err() == nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to get space edges to send to client") + } + return + } + payload.InvitedRooms, err = h.DB.InvitedRoom.GetAll(ctx) + if err != nil { + if ctx.Err() == nil { + zerolog.Ctx(ctx).Err(err).Msg("Failed to get invited rooms to send to client") + } + return + } + payload.ClearState = true + if !yield(&payload) { + return + } + } for i := 0; ; i++ { rooms, err := h.DB.Room.GetBySortTS(ctx, maxTS, batchSize) if err != nil { @@ -77,24 +120,6 @@ func (h *HiClient) GetInitialSync(ctx context.Context, batchSize int) iter.Seq[* payload := SyncComplete{ Rooms: make(map[id.RoomID]*SyncRoom, len(rooms)), } - if i == 0 { - payload.InvitedRooms, err = h.DB.InvitedRoom.GetAll(ctx) - if err != nil { - if ctx.Err() == nil { - zerolog.Ctx(ctx).Err(err).Msg("Failed to get invited rooms to send to client") - } - return - } - // TODO include space rooms in first batch too? - payload.SpaceEdges, err = h.DB.SpaceEdge.GetAll(ctx, "") - if err != nil { - if ctx.Err() == nil { - zerolog.Ctx(ctx).Err(err).Msg("Failed to get space edges to send to client") - } - return - } - payload.ClearState = true - } for _, room := range rooms { if room.SortingTimestamp == rooms[len(rooms)-1].SortingTimestamp { break @@ -105,7 +130,9 @@ func (h *HiClient) GetInitialSync(ctx context.Context, batchSize int) iter.Seq[* return } } - if !yield(&payload) || len(rooms) < batchSize { + if !yield(&payload) { + return + } else if len(rooms) < batchSize { break } } diff --git a/web/src/api/types/hievents.ts b/web/src/api/types/hievents.ts index f7d85d3..7c64596 100644 --- a/web/src/api/types/hievents.ts +++ b/web/src/api/types/hievents.ts @@ -92,6 +92,7 @@ export interface SyncCompleteData { left_rooms: RoomID[] | null account_data: Record | null space_edges: Record[]> | null + top_level_spaces: RoomID[] | null since?: string clear_state?: boolean }