Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Fix and clean up publicRooms pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
erikjohnston committed Sep 17, 2016
1 parent 995f2f0 commit 71edaae
Showing 1 changed file with 123 additions and 110 deletions.
233 changes: 123 additions & 110 deletions synapse/handlers/room_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,134 +115,63 @@ def get_order_for_room(room_id):
sorted_entries = sorted(rooms_to_order_value.items(), key=lambda e: e[1])
sorted_rooms = [room_id for room_id, _ in sorted_entries]

# `sorted_rooms` should now be a list of all public room ids that is
# stable across pagination. Therefore, we can use indices into this
# list as our pagination tokens.

# Filter out rooms that we don't want to return
rooms_to_scan = [
r for r in sorted_rooms
if r not in newly_unpublished and rooms_to_num_joined[room_id] > 0
]

if since_token:
# Filter out rooms we've already returned previously
# `since_token.current_limit` is the index of the last room we
# sent down, so we exclude it and everything before/after it.
if since_token.direction_is_forward:
sorted_rooms = sorted_rooms[since_token.current_limit:]
rooms_to_scan = rooms_to_scan[since_token.current_limit + 1:]
else:
sorted_rooms = sorted_rooms[:since_token.current_limit]
sorted_rooms.reverse()
rooms_to_scan = rooms_to_scan[:since_token.current_limit]
rooms_to_scan.reverse()

rooms_to_scan = sorted_rooms
# If there's not search filter just limit the range since we'll
# return the vast majority of things.
if limit and not search_filter:
rooms_to_scan = sorted_rooms[:limit + 1]
rooms_to_scan = rooms_to_scan[:limit + 1]

# Actually generate the entries. _generate_room_entry will append to
# chunk but will stop if len(chunk) > limit
chunk = []

@defer.inlineCallbacks
def handle_room(room_id):
if limit and len(chunk) > limit + 1:
# We've already got enough, so lets just drop it.
return

num_joined_users = rooms_to_num_joined[room_id]
if num_joined_users == 0:
return

if room_id in newly_unpublished:
return

result = {
"room_id": room_id,
"num_joined_members": num_joined_users,
}

current_state_ids = yield self.state_handler.get_current_state_ids(room_id)

event_map = yield self.store.get_events([
event_id for key, event_id in current_state_ids.items()
if key[0] in (
EventTypes.JoinRules,
EventTypes.Name,
EventTypes.Topic,
EventTypes.CanonicalAlias,
EventTypes.RoomHistoryVisibility,
EventTypes.GuestAccess,
"m.room.avatar",
)
])

current_state = {
(ev.type, ev.state_key): ev
for ev in event_map.values()
}

# Double check that this is actually a public room.
join_rules_event = current_state.get((EventTypes.JoinRules, ""))
if join_rules_event:
join_rule = join_rules_event.content.get("join_rule", None)
if join_rule and join_rule != JoinRules.PUBLIC:
defer.returnValue(None)

aliases = yield self.store.get_aliases_for_room(room_id)
if aliases:
result["aliases"] = aliases

name_event = yield current_state.get((EventTypes.Name, ""))
if name_event:
name = name_event.content.get("name", None)
if name:
result["name"] = name

topic_event = current_state.get((EventTypes.Topic, ""))
if topic_event:
topic = topic_event.content.get("topic", None)
if topic:
result["topic"] = topic

canonical_event = current_state.get((EventTypes.CanonicalAlias, ""))
if canonical_event:
canonical_alias = canonical_event.content.get("alias", None)
if canonical_alias:
result["canonical_alias"] = canonical_alias

visibility_event = current_state.get((EventTypes.RoomHistoryVisibility, ""))
visibility = None
if visibility_event:
visibility = visibility_event.content.get("history_visibility", None)
result["world_readable"] = visibility == "world_readable"

guest_event = current_state.get((EventTypes.GuestAccess, ""))
guest = None
if guest_event:
guest = guest_event.content.get("guest_access", None)
result["guest_can_join"] = guest == "can_join"

avatar_event = current_state.get(("m.room.avatar", ""))
if avatar_event:
avatar_url = avatar_event.content.get("url", None)
if avatar_url:
result["avatar_url"] = avatar_url

if _matches_room_entry(result, search_filter):
chunk.append(result)

yield concurrently_execute(handle_room, rooms_to_scan, 10)
yield concurrently_execute(
lambda r: self._generate_room_entry(
r, rooms_to_num_joined[r],
chunk, limit, search_filter
),
rooms_to_scan, 10
)

chunk.sort(key=lambda e: (-e["num_joined_members"], e["room_id"]))

# Work out the new limit of the batch for pagination, or None if we
# know there are no more results that would be returned.
# i.e., [since_token.current_limit..new_limit] is the batch of rooms
# we've returned (or the reverse if we paginated backwards)
# We tried to pull out limit + 1 rooms above, so if we have <= limit
# then we know there are no more results to return
new_limit = None
if chunk and (not limit or len(chunk) > limit):
if limit:
chunk = chunk[:limit]

addition = 1
if since_token:
addition += since_token.current_limit

if not since_token or since_token.direction_is_forward:
if limit:
chunk = chunk[:limit]
last_room_id = chunk[-1]["room_id"]
else:
if limit:
chunk = chunk[-limit:]
last_room_id = chunk[0]["room_id"]
addition *= -1

try:
new_limit = sorted_rooms.index(last_room_id) + addition
if new_limit >= len(sorted_rooms):
new_limit = None
except ValueError:
pass
new_limit = sorted_rooms.index(last_room_id)

results = {
"chunk": chunk,
Expand All @@ -252,7 +181,7 @@ def handle_room(room_id):
results["new_rooms"] = bool(newly_visible)

if not since_token or since_token.direction_is_forward:
if new_limit:
if new_limit is not None:
results["next_batch"] = RoomListNextBatch(
stream_ordering=stream_token,
public_room_stream_id=public_room_stream_id,
Expand All @@ -263,9 +192,10 @@ def handle_room(room_id):
if since_token:
results["prev_batch"] = since_token.copy_and_replace(
direction_is_forward=False,
current_limit=since_token.current_limit + 1,
).to_token()
else:
if new_limit:
if new_limit is not None:
results["prev_batch"] = RoomListNextBatch(
stream_ordering=stream_token,
public_room_stream_id=public_room_stream_id,
Expand All @@ -276,10 +206,93 @@ def handle_room(room_id):
if since_token:
results["next_batch"] = since_token.copy_and_replace(
direction_is_forward=True,
current_limit=since_token.current_limit - 1,
).to_token()

defer.returnValue(results)

@defer.inlineCallbacks
def _generate_room_entry(self, room_id, num_joined_users, chunk, limit,
search_filter):
if limit and len(chunk) > limit + 1:
# We've already got enough, so lets just drop it.
return

result = {
"room_id": room_id,
"num_joined_members": num_joined_users,
}

current_state_ids = yield self.state_handler.get_current_state_ids(room_id)

event_map = yield self.store.get_events([
event_id for key, event_id in current_state_ids.items()
if key[0] in (
EventTypes.JoinRules,
EventTypes.Name,
EventTypes.Topic,
EventTypes.CanonicalAlias,
EventTypes.RoomHistoryVisibility,
EventTypes.GuestAccess,
"m.room.avatar",
)
])

current_state = {
(ev.type, ev.state_key): ev
for ev in event_map.values()
}

# Double check that this is actually a public room.
join_rules_event = current_state.get((EventTypes.JoinRules, ""))
if join_rules_event:
join_rule = join_rules_event.content.get("join_rule", None)
if join_rule and join_rule != JoinRules.PUBLIC:
defer.returnValue(None)

aliases = yield self.store.get_aliases_for_room(room_id)
if aliases:
result["aliases"] = aliases

name_event = yield current_state.get((EventTypes.Name, ""))
if name_event:
name = name_event.content.get("name", None)
if name:
result["name"] = name

topic_event = current_state.get((EventTypes.Topic, ""))
if topic_event:
topic = topic_event.content.get("topic", None)
if topic:
result["topic"] = topic

canonical_event = current_state.get((EventTypes.CanonicalAlias, ""))
if canonical_event:
canonical_alias = canonical_event.content.get("alias", None)
if canonical_alias:
result["canonical_alias"] = canonical_alias

visibility_event = current_state.get((EventTypes.RoomHistoryVisibility, ""))
visibility = None
if visibility_event:
visibility = visibility_event.content.get("history_visibility", None)
result["world_readable"] = visibility == "world_readable"

guest_event = current_state.get((EventTypes.GuestAccess, ""))
guest = None
if guest_event:
guest = guest_event.content.get("guest_access", None)
result["guest_can_join"] = guest == "can_join"

avatar_event = current_state.get(("m.room.avatar", ""))
if avatar_event:
avatar_url = avatar_event.content.get("url", None)
if avatar_url:
result["avatar_url"] = avatar_url

if _matches_room_entry(result, search_filter):
chunk.append(result)

@defer.inlineCallbacks
def get_remote_public_room_list(self, server_name, limit=None, since_token=None,
search_filter=None):
Expand Down

0 comments on commit 71edaae

Please sign in to comment.