16
16
import itertools
17
17
import logging
18
18
from 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
20
22
21
23
from synapse .api .constants import EventContentFields , EventTypes , HistoryVisibility
22
24
from synapse .api .errors import AuthError
@@ -54,7 +56,7 @@ async def get_space_summary(
54
56
max_rooms_per_space : Optional [int ] = None ,
55
57
) -> JsonDict :
56
58
"""
57
- Implementation of the space summary API
59
+ Implementation of the space summary C-S API
58
60
59
61
Args:
60
62
requester: user id of the user making this request
@@ -66,7 +68,7 @@ async def get_space_summary(
66
68
67
69
max_rooms_per_space: an optional limit on the number of child rooms we will
68
70
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 .
70
72
71
73
Returns:
72
74
summary dict to return
@@ -76,67 +78,153 @@ async def get_space_summary(
76
78
await self ._auth .check_user_in_room_or_world_readable (room_id , requester )
77
79
78
80
# the queue of rooms to process
79
- room_queue = deque ((room_id ,))
81
+ room_queue = deque ((_RoomQueueEntry ( room_id ) ,))
80
82
81
83
processed_rooms = set () # type: Set[str]
82
84
83
85
rooms_result = [] # type: List[JsonDict]
84
86
events_result = [] # type: List[JsonDict]
85
87
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]
87
149
88
150
while room_queue and len (rooms_result ) < MAX_ROOMS :
89
151
room_id = room_queue .popleft ()
90
152
logger .debug ("Processing room %s" , room_id )
91
153
processed_rooms .add (room_id )
92
154
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
+ )
104
158
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 )
107
161
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" ])
110
166
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 }
114
168
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 ,
133
199
)
200
+ )
201
+ return (room_entry ,), events_result
134
202
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
138
212
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
140
228
141
229
async def _build_room_entry (self , room_id : str ) -> JsonDict :
142
230
"""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]:
191
279
return (e for e in events if e .content .get ("via" ))
192
280
193
281
282
+ @attr .s (frozen = True , slots = True )
283
+ class _RoomQueueEntry :
284
+ room_id = attr .ib (type = str )
285
+
286
+
194
287
def _is_suggested_child_event (edge_event : EventBase ) -> bool :
195
288
suggested = edge_event .content .get ("suggested" )
196
289
if isinstance (suggested , bool ) and suggested :
0 commit comments