Skip to content

Commit 875269e

Browse files
authored
Add experimental and incomplete support for MSC4306: Thread Subscriptions. (#18674)
Implements: [MSC4306](https://github.com/matrix-org/matrix-spec-proposals/blob/rei/msc_thread_subscriptions/proposals/4306-thread-subscriptions.md) (partially) What's missing: - Changes to push rules Signed-off-by: Olivier 'reivilibre <oliverw@matrix.org>
1 parent 56f5097 commit 875269e

25 files changed

+1522
-3
lines changed

changelog.d/18674.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add experimental and incomplete support for [MSC4306: Thread Subscriptions](https://github.com/matrix-org/matrix-spec-proposals/blob/rei/msc_thread_subscriptions/proposals/4306-thread-subscriptions.md).

docker/configure_workers_and_start.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,15 @@
327327
"shared_extra_conf": {},
328328
"worker_extra_conf": "",
329329
},
330+
"thread_subscriptions": {
331+
"app": "synapse.app.generic_worker",
332+
"listener_resources": ["client", "replication"],
333+
"endpoint_patterns": [
334+
"^/_matrix/client/unstable/io.element.msc4306/.*",
335+
],
336+
"shared_extra_conf": {},
337+
"worker_extra_conf": "",
338+
},
330339
}
331340

332341
# Templates for sections that may be inserted multiple times in config files
@@ -427,6 +436,7 @@ def add_worker_roles_to_shared_config(
427436
"to_device",
428437
"typing",
429438
"push_rules",
439+
"thread_subscriptions",
430440
}
431441

432442
# Worker-type specific sharding config. Now a single worker can fulfill multiple

synapse/_scripts/synapse_port_db.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
"has_known_state",
137137
"is_encrypted",
138138
],
139+
"thread_subscriptions": ["subscribed", "automatic"],
139140
"users": ["shadow_banned", "approved", "locked", "suspended"],
140141
"un_partial_stated_event_stream": ["rejection_status_changed"],
141142
"users_who_share_rooms": ["share_private"],

synapse/app/generic_worker.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@
104104
from synapse.storage.databases.main.stream import StreamWorkerStore
105105
from synapse.storage.databases.main.tags import TagsWorkerStore
106106
from synapse.storage.databases.main.task_scheduler import TaskSchedulerWorkerStore
107+
from synapse.storage.databases.main.thread_subscriptions import (
108+
ThreadSubscriptionsWorkerStore,
109+
)
107110
from synapse.storage.databases.main.transactions import TransactionWorkerStore
108111
from synapse.storage.databases.main.ui_auth import UIAuthWorkerStore
109112
from synapse.storage.databases.main.user_directory import UserDirectoryStore
@@ -132,6 +135,7 @@ class GenericWorkerStore(
132135
KeyStore,
133136
RoomWorkerStore,
134137
DirectoryWorkerStore,
138+
ThreadSubscriptionsWorkerStore,
135139
PushRulesWorkerStore,
136140
ApplicationServiceTransactionWorkerStore,
137141
ApplicationServiceWorkerStore,

synapse/config/experimental.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,3 +581,7 @@ def read_config(
581581

582582
# MSC4155: Invite filtering
583583
self.msc4155_enabled: bool = experimental.get("msc4155_enabled", False)
584+
585+
# MSC4306: Thread Subscriptions
586+
# (and MSC4308: sliding sync extension for thread subscriptions)
587+
self.msc4306_enabled: bool = experimental.get("msc4306_enabled", False)

synapse/config/workers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ class WriterLocations:
174174
default=[MAIN_PROCESS_INSTANCE_NAME],
175175
converter=_instance_to_list_converter,
176176
)
177+
thread_subscriptions: List[str] = attr.ib(
178+
default=["master"],
179+
converter=_instance_to_list_converter,
180+
)
177181

178182

179183
@attr.s(auto_attribs=True)

synapse/handlers/deactivate_account.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ async def deactivate_account(
187187
# Remove account data (including ignored users and push rules).
188188
await self.store.purge_account_data_for_user(user_id)
189189

190+
# Remove thread subscriptions for the user
191+
await self.store.purge_thread_subscription_settings_for_user(user_id)
192+
190193
# Delete any server-side backup keys
191194
await self.store.bulk_delete_backup_keys_and_versions_for_user(user_id)
192195

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import logging
2+
from typing import TYPE_CHECKING, Optional
3+
4+
from synapse.api.errors import AuthError, NotFoundError
5+
from synapse.storage.databases.main.thread_subscriptions import ThreadSubscription
6+
from synapse.types import UserID
7+
8+
if TYPE_CHECKING:
9+
from synapse.server import HomeServer
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
class ThreadSubscriptionsHandler:
15+
def __init__(self, hs: "HomeServer"):
16+
self.store = hs.get_datastores().main
17+
self.event_handler = hs.get_event_handler()
18+
self.auth = hs.get_auth()
19+
20+
async def get_thread_subscription_settings(
21+
self,
22+
user_id: UserID,
23+
room_id: str,
24+
thread_root_event_id: str,
25+
) -> Optional[ThreadSubscription]:
26+
"""Get thread subscription settings for a specific thread and user.
27+
Checks that the thread root is both a real event and also that it is visible
28+
to the user.
29+
30+
Args:
31+
user_id: The ID of the user
32+
thread_root_event_id: The event ID of the thread root
33+
34+
Returns:
35+
A `ThreadSubscription` containing the active subscription settings or None if not set
36+
"""
37+
# First check that the user can access the thread root event
38+
# and that it exists
39+
try:
40+
event = await self.event_handler.get_event(
41+
user_id, room_id, thread_root_event_id
42+
)
43+
if event is None:
44+
raise NotFoundError("No such thread root")
45+
except AuthError:
46+
raise NotFoundError("No such thread root")
47+
48+
return await self.store.get_subscription_for_thread(
49+
user_id.to_string(), event.room_id, thread_root_event_id
50+
)
51+
52+
async def subscribe_user_to_thread(
53+
self,
54+
user_id: UserID,
55+
room_id: str,
56+
thread_root_event_id: str,
57+
*,
58+
automatic: bool,
59+
) -> Optional[int]:
60+
"""Sets or updates a user's subscription settings for a specific thread root.
61+
62+
Args:
63+
requester_user_id: The ID of the user whose settings are being updated.
64+
thread_root_event_id: The event ID of the thread root.
65+
automatic: whether the user was subscribed by an automatic decision by
66+
their client.
67+
68+
Returns:
69+
The stream ID for this update, if the update isn't no-opped.
70+
71+
Raises:
72+
NotFoundError if the user cannot access the thread root event, or it isn't
73+
known to this homeserver.
74+
"""
75+
# First check that the user can access the thread root event
76+
# and that it exists
77+
try:
78+
event = await self.event_handler.get_event(
79+
user_id, room_id, thread_root_event_id
80+
)
81+
if event is None:
82+
raise NotFoundError("No such thread root")
83+
except AuthError:
84+
logger.info("rejecting thread subscriptions change (thread not accessible)")
85+
raise NotFoundError("No such thread root")
86+
87+
return await self.store.subscribe_user_to_thread(
88+
user_id.to_string(),
89+
event.room_id,
90+
thread_root_event_id,
91+
automatic=automatic,
92+
)
93+
94+
async def unsubscribe_user_from_thread(
95+
self, user_id: UserID, room_id: str, thread_root_event_id: str
96+
) -> Optional[int]:
97+
"""Clears a user's subscription settings for a specific thread root.
98+
99+
Args:
100+
requester_user_id: The ID of the user whose settings are being updated.
101+
thread_root_event_id: The event ID of the thread root.
102+
103+
Returns:
104+
The stream ID for this update, if the update isn't no-opped.
105+
106+
Raises:
107+
NotFoundError if the user cannot access the thread root event, or it isn't
108+
known to this homeserver.
109+
"""
110+
# First check that the user can access the thread root event
111+
# and that it exists
112+
try:
113+
event = await self.event_handler.get_event(
114+
user_id, room_id, thread_root_event_id
115+
)
116+
if event is None:
117+
raise NotFoundError("No such thread root")
118+
except AuthError:
119+
logger.info("rejecting thread subscriptions change (thread not accessible)")
120+
raise NotFoundError("No such thread root")
121+
122+
return await self.store.unsubscribe_user_from_thread(
123+
user_id.to_string(),
124+
event.room_id,
125+
thread_root_event_id,
126+
)

synapse/replication/tcp/handler.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@
7272
ToDeviceStream,
7373
TypingStream,
7474
)
75-
from synapse.replication.tcp.streams._base import DeviceListsStream
75+
from synapse.replication.tcp.streams._base import (
76+
DeviceListsStream,
77+
ThreadSubscriptionsStream,
78+
)
7679

7780
if TYPE_CHECKING:
7881
from synapse.server import HomeServer
@@ -186,6 +189,15 @@ def __init__(self, hs: "HomeServer"):
186189

187190
continue
188191

192+
if isinstance(stream, ThreadSubscriptionsStream):
193+
if (
194+
hs.get_instance_name()
195+
in hs.config.worker.writers.thread_subscriptions
196+
):
197+
self._streams_to_replicate.append(stream)
198+
199+
continue
200+
189201
if isinstance(stream, DeviceListsStream):
190202
if hs.get_instance_name() in hs.config.worker.writers.device_lists:
191203
self._streams_to_replicate.append(stream)

synapse/replication/tcp/streams/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
PushRulesStream,
4242
ReceiptsStream,
4343
Stream,
44+
ThreadSubscriptionsStream,
4445
ToDeviceStream,
4546
TypingStream,
4647
)
@@ -67,6 +68,7 @@
6768
ToDeviceStream,
6869
FederationStream,
6970
AccountDataStream,
71+
ThreadSubscriptionsStream,
7072
UnPartialStatedRoomStream,
7173
UnPartialStatedEventStream,
7274
)
@@ -86,6 +88,7 @@
8688
"DeviceListsStream",
8789
"ToDeviceStream",
8890
"AccountDataStream",
91+
"ThreadSubscriptionsStream",
8992
"UnPartialStatedRoomStream",
9093
"UnPartialStatedEventStream",
9194
]

0 commit comments

Comments
 (0)