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

Add room context api #334

Merged
merged 2 commits into from
Oct 28, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion synapse/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from synapse.appservice.api import ApplicationServiceApi
from .register import RegistrationHandler
from .room import (
RoomCreationHandler, RoomMemberHandler, RoomListHandler
RoomCreationHandler, RoomMemberHandler, RoomListHandler, RoomContextHandler,
)
from .message import MessageHandler
from .events import EventStreamHandler, EventHandler
Expand Down Expand Up @@ -70,3 +70,4 @@ def __init__(self, hs):
self.auth_handler = AuthHandler(hs)
self.identity_handler = IdentityHandler(hs)
self.search_handler = SearchHandler(hs)
self.room_context_handler = RoomContextHandler(hs)
55 changes: 55 additions & 0 deletions synapse/handlers/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
from unpaddedbase64 import decode_base64

import logging
import math
import string

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -747,6 +748,60 @@ def get_public_room_list(self):
defer.returnValue({"start": "START", "end": "END", "chunk": chunk})


class RoomContextHandler(BaseHandler):
@defer.inlineCallbacks
def get_event_context(self, user, room_id, event_id, limit):
"""Retrieves events, pagination tokens and state around a given event
in a room.

Args:
user (UserID)
room_id (str)
event_id (str)
limit (int): The maximum number of events to return in total
(excluding state).

Returns:
dict
"""
before_limit = math.floor(limit/2.)
after_limit = limit - before_limit

now_token = yield self.hs.get_event_sources().get_current_token()

results = yield self.store.get_events_around(
room_id, event_id, before_limit, after_limit
)

results["events_before"] = yield self._filter_events_for_client(
user.to_string(), results["events_before"]
)

results["events_after"] = yield self._filter_events_for_client(
user.to_string(), results["events_after"]
)

if results["events_after"]:
last_event_id = results["events_after"][-1].event_id
else:
last_event_id = event_id

state = yield self.store.get_state_for_events(
[last_event_id], None
)
results["state"] = state[last_event_id].values()

results["start"] = now_token.copy_and_replace(
"room_key", results["start"]
).to_string()

results["end"] = now_token.copy_and_replace(
"room_key", results["end"]
).to_string()

defer.returnValue(results)


class RoomEventSource(object):
def __init__(self, hs):
self.store = hs.get_datastore()
Expand Down
36 changes: 36 additions & 0 deletions synapse/rest/client/v1/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,41 @@ def on_GET(self, request, room_id):
defer.returnValue((200, res))


class RoomEventContext(ClientV1RestServlet):
PATTERN = client_path_pattern(
"/rooms/(?P<room_id>[^/]*)/context/(?P<event_id>[^/]*)$"
)

def __init__(self, hs):
super(RoomEventContext, self).__init__(hs)
self.clock = hs.get_clock()

@defer.inlineCallbacks
def on_GET(self, request, room_id, event_id):
user, _ = yield self.auth.get_user_by_req(request)

limit = int(request.args.get("limit", [10])[0])

results = yield self.handlers.room_context_handler.get_event_context(
user, room_id, event_id, limit,
)

time_now = self.clock.time_msec()
results["events_before"] = [
serialize_event(event, time_now) for event in results["events_before"]
]
results["events_after"] = [
serialize_event(event, time_now) for event in results["events_after"]
]
results["state"] = [
serialize_event(event, time_now) for event in results["state"]
]

logger.info("Responding with %r", results)

defer.returnValue((200, results))


# TODO: Needs unit testing
class RoomMembershipRestServlet(ClientV1RestServlet):

Expand Down Expand Up @@ -628,3 +663,4 @@ def register_servlets(hs, http_server):
RoomRedactEventRestServlet(hs).register(http_server)
RoomTypingRestServlet(hs).register(http_server)
SearchRestServlet(hs).register(http_server)
RoomEventContext(hs).register(http_server)
137 changes: 136 additions & 1 deletion synapse/storage/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

This is implemented by keeping two ordering columns: stream_ordering and
topological_ordering. Stream ordering is basically insertion/received order
(except for events from backfill requests). The topolgical_ordering is a
(except for events from backfill requests). The topological_ordering is a
weak ordering of events based on the pdu graph.

This means that we have to have two different types of tokens, depending on
Expand Down Expand Up @@ -436,3 +436,138 @@ def _set_before_and_after(events, rows):
internal = event.internal_metadata
internal.before = str(RoomStreamToken(topo, stream - 1))
internal.after = str(RoomStreamToken(topo, stream))

@defer.inlineCallbacks
def get_events_around(self, room_id, event_id, before_limit, after_limit):
"""Retrieve events and pagination tokens around a given event in a
room.

Args:
room_id (str)
event_id (str)
before_limit (int)
after_limit (int)

Returns:
dict
"""

results = yield self.runInteraction(
"get_events_around", self._get_events_around_txn,
room_id, event_id, before_limit, after_limit
)

events_before = yield self._get_events(
[e for e in results["before"]["event_ids"]],
get_prev_content=True
)

events_after = yield self._get_events(
[e for e in results["after"]["event_ids"]],
get_prev_content=True
)

defer.returnValue({
"events_before": events_before,
"events_after": events_after,
"start": results["before"]["token"],
"end": results["after"]["token"],
})

def _get_events_around_txn(self, txn, room_id, event_id, before_limit, after_limit):
"""Retrieves event_ids and pagination tokens around a given event in a
room.

Args:
room_id (str)
event_id (str)
before_limit (int)
after_limit (int)

Returns:
dict
"""

results = self._simple_select_one_txn(
txn,
"events",
keyvalues={
"event_id": event_id,
"room_id": room_id,
},
retcols=["stream_ordering", "topological_ordering"],
)

stream_ordering = results["stream_ordering"]
topological_ordering = results["topological_ordering"]

query_before = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be quite nice to have this query written once with a substitution for ASC/DESC, rather than written twice

"SELECT topological_ordering, stream_ordering, event_id FROM events"
" WHERE room_id = ? AND (topological_ordering < ?"
" OR (topological_ordering = ? AND stream_ordering < ?))"
" ORDER BY topological_ordering DESC, stream_ordering DESC"
" LIMIT ?"
)

query_after = (
"SELECT topological_ordering, stream_ordering, event_id FROM events"
" WHERE room_id = ? AND (topological_ordering > ?"
" OR (topological_ordering = ? AND stream_ordering > ?))"
" ORDER BY topological_ordering ASC, stream_ordering ASC"
" LIMIT ?"
)

txn.execute(
query_before,
(
room_id, topological_ordering, topological_ordering,
stream_ordering, before_limit,
)
)

rows = self.cursor_to_dict(txn)
events_before = [r["event_id"] for r in rows]

if rows:
start_token = str(RoomStreamToken(
rows[0]["topological_ordering"],
rows[0]["stream_ordering"] - 1,
))
else:
start_token = str(RoomStreamToken(
topological_ordering,
stream_ordering - 1,
))

txn.execute(
query_after,
(
room_id, topological_ordering, topological_ordering,
stream_ordering, after_limit,
)
)

rows = self.cursor_to_dict(txn)
events_after = [r["event_id"] for r in rows]

if rows:
end_token = str(RoomStreamToken(
rows[-1]["topological_ordering"],
rows[-1]["stream_ordering"],
))
else:
end_token = str(RoomStreamToken(
topological_ordering,
stream_ordering,
))

return {
"before": {
"event_ids": events_before,
"token": start_token,
},
"after": {
"event_ids": events_after,
"token": end_token,
},
}