1616import itertools
1717import logging
1818from collections import deque
19- from typing import TYPE_CHECKING , Iterable , List , Optional , Set
19+ from typing import TYPE_CHECKING , Iterable , List , Optional , Sequence , Set , Tuple
20+
21+ import attr
2022
2123from synapse .api .constants import EventContentFields , EventTypes , HistoryVisibility
2224from synapse .api .errors import AuthError
@@ -54,7 +56,7 @@ async def get_space_summary(
5456 max_rooms_per_space : Optional [int ] = None ,
5557 ) -> JsonDict :
5658 """
57- Implementation of the space summary API
59+ Implementation of the space summary C-S API
5860
5961 Args:
6062 requester: user id of the user making this request
@@ -66,7 +68,7 @@ async def get_space_summary(
6668
6769 max_rooms_per_space: an optional limit on the number of child rooms we will
6870 return. This does not apply to the root room (ie, room_id), and
69- is overridden by ROOMS_PER_SPACE_LIMIT .
71+ is overridden by MAX_ROOMS_PER_SPACE .
7072
7173 Returns:
7274 summary dict to return
@@ -76,67 +78,153 @@ async def get_space_summary(
7678 await self ._auth .check_user_in_room_or_world_readable (room_id , requester )
7779
7880 # the queue of rooms to process
79- room_queue = deque ((room_id ,))
81+ room_queue = deque ((_RoomQueueEntry ( room_id ) ,))
8082
8183 processed_rooms = set () # type: Set[str]
8284
8385 rooms_result = [] # type: List[JsonDict]
8486 events_result = [] # type: List[JsonDict]
8587
86- now = self ._clock .time_msec ()
88+ while room_queue and len (rooms_result ) < MAX_ROOMS :
89+ queue_entry = room_queue .popleft ()
90+ room_id = queue_entry .room_id
91+ logger .debug ("Processing room %s" , room_id )
92+ processed_rooms .add (room_id )
93+
94+ # The client-specified max_rooms_per_space limit doesn't apply to the
95+ # room_id specified in the request, so we ignore it if this is the
96+ # first room we are processing.
97+ max_children = max_rooms_per_space if processed_rooms else None
98+
99+ rooms , events = await self ._summarize_local_room (
100+ requester , room_id , suggested_only , max_children
101+ )
102+
103+ rooms_result .extend (rooms )
104+ events_result .extend (events )
105+
106+ # add any children that we haven't already processed to the queue
107+ for edge_event in events :
108+ if edge_event ["state_key" ] not in processed_rooms :
109+ room_queue .append (_RoomQueueEntry (edge_event ["state_key" ]))
110+
111+ return {"rooms" : rooms_result , "events" : events_result }
112+
113+ async def federation_space_summary (
114+ self ,
115+ room_id : str ,
116+ suggested_only : bool ,
117+ max_rooms_per_space : Optional [int ],
118+ exclude_rooms : Iterable [str ],
119+ ) -> JsonDict :
120+ """
121+ Implementation of the space summary Federation API
122+
123+ Args:
124+ room_id: room id to start the summary at
125+
126+ suggested_only: whether we should only return children with the "suggested"
127+ flag set.
128+
129+ max_rooms_per_space: an optional limit on the number of child rooms we will
130+ return. Unlike the C-S API, this applies to the root room (room_id).
131+ It is clipped to MAX_ROOMS_PER_SPACE.
132+
133+ exclude_rooms: a list of rooms to skip over (presumably because the
134+ calling server has already seen them).
135+
136+ Returns:
137+ summary dict to return
138+ """
139+ # the queue of rooms to process
140+ room_queue = deque ((room_id ,))
141+
142+ # the set of rooms that we should not walk further. Initialise it with the
143+ # excluded-rooms list; we will add other rooms as we process them so that
144+ # we do not loop.
145+ processed_rooms = set (exclude_rooms ) # type: Set[str]
146+
147+ rooms_result = [] # type: List[JsonDict]
148+ events_result = [] # type: List[JsonDict]
87149
88150 while room_queue and len (rooms_result ) < MAX_ROOMS :
89151 room_id = room_queue .popleft ()
90152 logger .debug ("Processing room %s" , room_id )
91153 processed_rooms .add (room_id )
92154
93- try :
94- await self ._auth .check_user_in_room_or_world_readable (
95- room_id , requester
96- )
97- except AuthError :
98- logger .info (
99- "user %s cannot view room %s, omitting from summary" ,
100- requester ,
101- room_id ,
102- )
103- continue
155+ rooms , events = await self ._summarize_local_room (
156+ None , room_id , suggested_only , max_rooms_per_space
157+ )
104158
105- room_entry = await self . _build_room_entry ( room_id )
106- rooms_result . append ( room_entry )
159+ rooms_result . extend ( rooms )
160+ events_result . extend ( events )
107161
108- # look for child rooms/spaces.
109- child_events = await self ._get_child_events (room_id )
162+ # add any children that we haven't already processed to the queue
163+ for edge_event in events :
164+ if edge_event ["state_key" ] not in processed_rooms :
165+ room_queue .append (edge_event ["state_key" ])
110166
111- if suggested_only :
112- # we only care about suggested children
113- child_events = filter (_is_suggested_child_event , child_events )
167+ return {"rooms" : rooms_result , "events" : events_result }
114168
115- # The client-specified max_rooms_per_space limit doesn't apply to the
116- # room_id specified in the request, so we ignore it if this is the
117- # first room we are processing. Otherwise, apply any client-specified
118- # limit, capping to our built-in limit.
119- if max_rooms_per_space is not None and len (processed_rooms ) > 1 :
120- max_rooms = min (MAX_ROOMS_PER_SPACE , max_rooms_per_space )
121- else :
122- max_rooms = MAX_ROOMS_PER_SPACE
123-
124- for edge_event in itertools .islice (child_events , max_rooms ):
125- edge_room_id = edge_event .state_key
126-
127- events_result .append (
128- await self ._event_serializer .serialize_event (
129- edge_event ,
130- time_now = now ,
131- event_format = format_event_for_client_v2 ,
132- )
169+ async def _summarize_local_room (
170+ self ,
171+ requester : Optional [str ],
172+ room_id : str ,
173+ suggested_only : bool ,
174+ max_children : Optional [int ],
175+ ) -> Tuple [Sequence [JsonDict ], Sequence [JsonDict ]]:
176+ if not await self ._is_room_accessible (room_id , requester ):
177+ return (), ()
178+
179+ room_entry = await self ._build_room_entry (room_id )
180+
181+ # look for child rooms/spaces.
182+ child_events = await self ._get_child_events (room_id )
183+
184+ if suggested_only :
185+ # we only care about suggested children
186+ child_events = filter (_is_suggested_child_event , child_events )
187+
188+ if max_children is None or max_children > MAX_ROOMS_PER_SPACE :
189+ max_children = MAX_ROOMS_PER_SPACE
190+
191+ now = self ._clock .time_msec ()
192+ events_result = [] # type: List[JsonDict]
193+ for edge_event in itertools .islice (child_events , max_children ):
194+ events_result .append (
195+ await self ._event_serializer .serialize_event (
196+ edge_event ,
197+ time_now = now ,
198+ event_format = format_event_for_client_v2 ,
133199 )
200+ )
201+ return (room_entry ,), events_result
134202
135- # if we haven't yet visited the target of this link, add it to the queue
136- if edge_room_id not in processed_rooms :
137- room_queue .append (edge_room_id )
203+ async def _is_room_accessible (self , room_id : str , requester : Optional [str ]) -> bool :
204+ # if we have an authenticated requesting user, first check if they are in the
205+ # room
206+ if requester :
207+ try :
208+ await self ._auth .check_user_in_room (room_id , requester )
209+ return True
210+ except AuthError :
211+ pass
138212
139- return {"rooms" : rooms_result , "events" : events_result }
213+ # otherwise, check if the room is peekable
214+ hist_vis_ev = await self ._state_handler .get_current_state (
215+ room_id , EventTypes .RoomHistoryVisibility , ""
216+ )
217+ if hist_vis_ev :
218+ hist_vis = hist_vis_ev .content .get ("history_visibility" )
219+ if hist_vis == HistoryVisibility .WORLD_READABLE :
220+ return True
221+
222+ logger .info (
223+ "room %s is unpeekable and user %s is not a member, omitting from summary" ,
224+ room_id ,
225+ requester ,
226+ )
227+ return False
140228
141229 async def _build_room_entry (self , room_id : str ) -> JsonDict :
142230 """Generate en entry suitable for the 'rooms' list in the summary response"""
@@ -191,6 +279,11 @@ async def _get_child_events(self, room_id: str) -> Iterable[EventBase]:
191279 return (e for e in events if e .content .get ("via" ))
192280
193281
282+ @attr .s (frozen = True , slots = True )
283+ class _RoomQueueEntry :
284+ room_id = attr .ib (type = str )
285+
286+
194287def _is_suggested_child_event (edge_event : EventBase ) -> bool :
195288 suggested = edge_event .content .get ("suggested" )
196289 if isinstance (suggested , bool ) and suggested :
0 commit comments