diff --git a/synapse/rest/client/v1/room.py b/synapse/rest/client/v1/room.py
index fcfe7857f6..218dbb93ff 100644
--- a/synapse/rest/client/v1/room.py
+++ b/synapse/rest/client/v1/room.py
@@ -825,6 +825,30 @@ class JoinedRoomsRestServlet(ClientV1RestServlet):
defer.returnValue((200, {"joined_rooms": list(room_ids)}))
+class TimestampLookupRestServlet(ClientV1RestServlet):
+ PATTERNS = client_path_patterns("/rooms/(?P<room_id>[^/]*)/timestamp_to_event$")
+
+ def __init__(self, hs):
+ super(TimestampLookupRestServlet, self).__init__(hs)
+ self.store = hs.get_datastore()
+
+ @defer.inlineCallbacks
+ def on_GET(self, request, room_id):
+ requester = yield self.auth.get_user_by_req(request)
+ yield self.auth.check_joined_room(room_id, requester.user.to_string())
+
+ timestamp = parse_integer(request, "ts")
+ thread_id = parse_integer(request, "thread_id", 0)
+
+ event_id = yield self.store.get_event_for_timestamp(
+ room_id, thread_id, timestamp,
+ )
+
+ defer.returnValue((200, {
+ "event_id": event_id,
+ }))
+
+
def register_txn_path(servlet, regex_string, http_server, with_get=False):
"""Registers a transaction-based path.
@@ -874,6 +898,7 @@ def register_servlets(hs, http_server):
JoinedRoomsRestServlet(hs).register(http_server)
RoomEventServlet(hs).register(http_server)
RoomEventContextServlet(hs).register(http_server)
+ TimestampLookupRestServlet(hs).register(http_server)
def register_deprecated_servlets(hs, http_server):
diff --git a/synapse/storage/events_worker.py b/synapse/storage/events_worker.py
index a8326f5296..e4d8562396 100644
--- a/synapse/storage/events_worker.py
+++ b/synapse/storage/events_worker.py
@@ -526,3 +526,43 @@ class EventsWorkerStore(SQLBaseStore):
return res
return self.runInteraction("get_rejection_reasons", f)
+
+ def get_event_for_timestamp(self, room_id, thread_id, timestamp):
+ sql_template = """
+ SELECT event_id, origin_server_ts FROM events
+ WHERE
+ origin_server_ts %s ?
+ AND room_id = ?
+ AND thread_id = ?
+ ORDER BY origin_server_ts
+ LIMIT 1;
+ """
+
+ def f(txn):
+ txn.execute(sql_template % ("<=",), (timestamp, room_id, thread_id))
+ row = txn.fetchone()
+ if row:
+ event_id_before, ts_before = row
+ else:
+ event_id_before, ts_before = None, None
+
+ txn.execute(sql_template % (">=",), (timestamp, room_id, thread_id))
+ row = txn.fetchone()
+ if row:
+ event_id_after, ts_after = row
+ else:
+ event_id_after, ts_after = None, None
+
+ if event_id_before and event_id_before:
+ # Return the closest one
+ if (timestamp - ts_before) < (ts_after - timestamp):
+ return event_id_before
+ else:
+ return event_id_after
+
+ if event_id_before:
+ return event_id_before
+
+ return event_id_after
+
+ return self.runInteraction("get_event_for_timestamp", f)
|