Skip to content

Commit 7c89612

Browse files
committed
Always include the user's receipts
1 parent 3e36aff commit 7c89612

File tree

2 files changed

+115
-22
lines changed

2 files changed

+115
-22
lines changed

synapse/handlers/sliding_sync/extensions.py

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,21 @@
1212
# <https://www.gnu.org/licenses/agpl-3.0.html>.
1313
#
1414

15+
import itertools
1516
import logging
1617
from typing import TYPE_CHECKING, Dict, List, Mapping, Optional, Sequence, Set
1718

1819
from typing_extensions import assert_never
1920

20-
from synapse.api.constants import AccountDataTypes
21+
from synapse.api.constants import AccountDataTypes, EduTypes
2122
from synapse.handlers.receipts import ReceiptEventSource
2223
from synapse.handlers.sliding_sync.types import (
2324
HaveSentRoomFlag,
2425
MutablePerConnectionState,
2526
PerConnectionState,
2627
)
2728
from synapse.logging.opentracing import trace
29+
from synapse.storage.databases.main.receipts import ReceiptInRoom
2830
from synapse.types import (
2931
DeviceListUpdates,
3032
JsonMapping,
@@ -541,21 +543,49 @@ async def get_receipts_extension_response(
541543
)
542544
fetched_receipts.extend(previously_receipts)
543545

544-
# For rooms we haven't previously sent down, we could send all receipts
545-
# from that room but we only want to include receipts for events
546-
# in the timeline to avoid bloating and blowing up the sync response
547-
# as the number of users in the room increases. (this behavior is part of the spec)
548-
initial_rooms_and_event_ids = [
549-
(room_id, event.event_id)
550-
for room_id in initial_rooms
551-
if room_id in actual_room_response_map
552-
for event in actual_room_response_map[room_id].timeline_events
553-
]
554-
if initial_rooms_and_event_ids:
546+
if initial_rooms:
547+
# We also always send down receipts for the current user.
548+
user_receipts = (
549+
await self.store.get_linearized_receipts_for_user_in_rooms(
550+
user_id=sync_config.user.to_string(),
551+
room_ids=initial_rooms,
552+
to_key=to_token.receipt_key,
553+
)
554+
)
555+
556+
# For rooms we haven't previously sent down, we could send all receipts
557+
# from that room but we only want to include receipts for events
558+
# in the timeline to avoid bloating and blowing up the sync response
559+
# as the number of users in the room increases. (this behavior is part of the spec)
560+
initial_rooms_and_event_ids = [
561+
(room_id, event.event_id)
562+
for room_id in initial_rooms
563+
if room_id in actual_room_response_map
564+
for event in actual_room_response_map[room_id].timeline_events
565+
]
555566
initial_receipts = await self.store.get_linearized_receipts_for_events(
556567
room_and_event_ids=initial_rooms_and_event_ids,
557568
)
558-
fetched_receipts.extend(initial_receipts)
569+
570+
# Combine the receipts for a room and add them to
571+
# `fetched_receipts`
572+
for room_id in initial_receipts.keys() | user_receipts.keys():
573+
receipt_content = ReceiptInRoom.merge_to_content(
574+
list(
575+
itertools.chain(
576+
initial_receipts.get(room_id, []),
577+
user_receipts.get(room_id, []),
578+
)
579+
)
580+
)
581+
582+
fetched_receipts.append(
583+
{
584+
"room_id": room_id,
585+
"type": EduTypes.RECEIPT,
586+
"content": receipt_content,
587+
}
588+
)
559589

560590
fetched_receipts = ReceiptEventSource.filter_out_private_receipts(
561591
fetched_receipts, sync_config.user.to_string()

synapse/storage/databases/main/receipts.py

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ def f(
534534
async def get_linearized_receipts_for_events(
535535
self,
536536
room_and_event_ids: Collection[Tuple[str, str]],
537-
) -> Sequence[JsonMapping]:
537+
) -> Mapping[str, Sequence[ReceiptInRoom]]:
538538
"""Get all receipts for the given set of events.
539539
540540
Arguments:
@@ -544,6 +544,8 @@ async def get_linearized_receipts_for_events(
544544
Returns:
545545
A list of receipts, one per room.
546546
"""
547+
if not room_and_event_ids:
548+
return {}
547549

548550
def get_linearized_receipts_for_events_txn(
549551
txn: LoggingTransaction,
@@ -590,14 +592,7 @@ def get_linearized_receipts_for_events_txn(
590592
)
591593
)
592594

593-
return [
594-
{
595-
"type": EduTypes.RECEIPT,
596-
"room_id": room_id,
597-
"content": ReceiptInRoom.merge_to_content(receipts),
598-
}
599-
for room_id, receipts in room_to_receipts.items()
600-
]
595+
return room_to_receipts
601596

602597
@cached(
603598
num_args=2,
@@ -670,6 +665,74 @@ def f(txn: LoggingTransaction) -> List[Tuple[str, str, str, str, str]]:
670665

671666
return results
672667

668+
async def get_linearized_receipts_for_user_in_rooms(
669+
self, user_id: str, room_ids: StrCollection, to_key: MultiWriterStreamToken
670+
) -> Mapping[str, Sequence[ReceiptInRoom]]:
671+
"""Fetch all receipts for the user in the given room.
672+
673+
Returns:
674+
A dict from room ID to receipts in the room.
675+
"""
676+
677+
def get_linearized_receipts_for_user_in_rooms_txn(
678+
txn: LoggingTransaction,
679+
batch_room_ids: StrCollection,
680+
) -> List[Tuple[str, str, str, str, Optional[str], str]]:
681+
clause, args = make_in_list_sql_clause(
682+
self.database_engine, "room_id", batch_room_ids
683+
)
684+
685+
sql = f"""
686+
SELECT instance_name, stream_id, room_id, receipt_type, user_id, event_id, thread_id, data
687+
FROM receipts_linearized
688+
WHERE {clause} AND user_id = ? AND stream_id <= ?
689+
"""
690+
691+
args.append(user_id)
692+
args.append(to_key.get_max_stream_pos())
693+
694+
txn.execute(sql, args)
695+
696+
return [
697+
(room_id, receipt_type, user_id, event_id, thread_id, data)
698+
for instance_name, stream_id, room_id, receipt_type, user_id, event_id, thread_id, data in txn
699+
if MultiWriterStreamToken.is_stream_position_in_range(
700+
low=None,
701+
high=to_key,
702+
instance_name=instance_name,
703+
pos=stream_id,
704+
)
705+
]
706+
707+
# room_id -> receipts
708+
room_to_receipts: Dict[str, List[ReceiptInRoom]] = {}
709+
for batch in batch_iter(room_ids, 1000):
710+
batch_results = await self.db_pool.runInteraction(
711+
"get_linearized_receipts_for_events",
712+
get_linearized_receipts_for_user_in_rooms_txn,
713+
batch,
714+
)
715+
716+
for (
717+
room_id,
718+
receipt_type,
719+
user_id,
720+
event_id,
721+
thread_id,
722+
data,
723+
) in batch_results:
724+
room_to_receipts.setdefault(room_id, []).append(
725+
ReceiptInRoom(
726+
receipt_type=receipt_type,
727+
user_id=user_id,
728+
event_id=event_id,
729+
thread_id=thread_id,
730+
data=db_to_json(data),
731+
)
732+
)
733+
734+
return room_to_receipts
735+
673736
async def get_rooms_with_receipts_between(
674737
self,
675738
room_ids: StrCollection,

0 commit comments

Comments
 (0)