PageRenderTime 41ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/synapse/handlers/sync.py

https://gitlab.com/JigmeDatse/synapse
Python | 1131 lines | 750 code | 162 blank | 219 comment | 146 complexity | f772950822468bee89d754043bcb9809 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2015 - 2016 OpenMarket Ltd
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. from synapse.api.constants import Membership, EventTypes
  16. from synapse.util.async import concurrently_execute
  17. from synapse.util.logcontext import LoggingContext
  18. from synapse.util.metrics import Measure
  19. from synapse.util.caches.response_cache import ResponseCache
  20. from synapse.push.clientformat import format_push_rules_for_user
  21. from synapse.visibility import filter_events_for_client
  22. from twisted.internet import defer
  23. import collections
  24. import logging
  25. import itertools
  26. logger = logging.getLogger(__name__)
  27. SyncConfig = collections.namedtuple("SyncConfig", [
  28. "user",
  29. "filter_collection",
  30. "is_guest",
  31. "request_key",
  32. ])
  33. class TimelineBatch(collections.namedtuple("TimelineBatch", [
  34. "prev_batch",
  35. "events",
  36. "limited",
  37. ])):
  38. __slots__ = []
  39. def __nonzero__(self):
  40. """Make the result appear empty if there are no updates. This is used
  41. to tell if room needs to be part of the sync result.
  42. """
  43. return bool(self.events)
  44. class JoinedSyncResult(collections.namedtuple("JoinedSyncResult", [
  45. "room_id", # str
  46. "timeline", # TimelineBatch
  47. "state", # dict[(str, str), FrozenEvent]
  48. "ephemeral",
  49. "account_data",
  50. "unread_notifications",
  51. ])):
  52. __slots__ = []
  53. def __nonzero__(self):
  54. """Make the result appear empty if there are no updates. This is used
  55. to tell if room needs to be part of the sync result.
  56. """
  57. return bool(
  58. self.timeline
  59. or self.state
  60. or self.ephemeral
  61. or self.account_data
  62. # nb the notification count does not, er, count: if there's nothing
  63. # else in the result, we don't need to send it.
  64. )
  65. class ArchivedSyncResult(collections.namedtuple("ArchivedSyncResult", [
  66. "room_id", # str
  67. "timeline", # TimelineBatch
  68. "state", # dict[(str, str), FrozenEvent]
  69. "account_data",
  70. ])):
  71. __slots__ = []
  72. def __nonzero__(self):
  73. """Make the result appear empty if there are no updates. This is used
  74. to tell if room needs to be part of the sync result.
  75. """
  76. return bool(
  77. self.timeline
  78. or self.state
  79. or self.account_data
  80. )
  81. class InvitedSyncResult(collections.namedtuple("InvitedSyncResult", [
  82. "room_id", # str
  83. "invite", # FrozenEvent: the invite event
  84. ])):
  85. __slots__ = []
  86. def __nonzero__(self):
  87. """Invited rooms should always be reported to the client"""
  88. return True
  89. class SyncResult(collections.namedtuple("SyncResult", [
  90. "next_batch", # Token for the next sync
  91. "presence", # List of presence events for the user.
  92. "account_data", # List of account_data events for the user.
  93. "joined", # JoinedSyncResult for each joined room.
  94. "invited", # InvitedSyncResult for each invited room.
  95. "archived", # ArchivedSyncResult for each archived room.
  96. ])):
  97. __slots__ = []
  98. def __nonzero__(self):
  99. """Make the result appear empty if there are no updates. This is used
  100. to tell if the notifier needs to wait for more events when polling for
  101. events.
  102. """
  103. return bool(
  104. self.presence or
  105. self.joined or
  106. self.invited or
  107. self.archived or
  108. self.account_data
  109. )
  110. class SyncHandler(object):
  111. def __init__(self, hs):
  112. self.store = hs.get_datastore()
  113. self.notifier = hs.get_notifier()
  114. self.presence_handler = hs.get_presence_handler()
  115. self.event_sources = hs.get_event_sources()
  116. self.clock = hs.get_clock()
  117. self.response_cache = ResponseCache()
  118. def wait_for_sync_for_user(self, sync_config, since_token=None, timeout=0,
  119. full_state=False):
  120. """Get the sync for a client if we have new data for it now. Otherwise
  121. wait for new data to arrive on the server. If the timeout expires, then
  122. return an empty sync result.
  123. Returns:
  124. A Deferred SyncResult.
  125. """
  126. result = self.response_cache.get(sync_config.request_key)
  127. if not result:
  128. result = self.response_cache.set(
  129. sync_config.request_key,
  130. self._wait_for_sync_for_user(
  131. sync_config, since_token, timeout, full_state
  132. )
  133. )
  134. return result
  135. @defer.inlineCallbacks
  136. def _wait_for_sync_for_user(self, sync_config, since_token, timeout,
  137. full_state):
  138. context = LoggingContext.current_context()
  139. if context:
  140. if since_token is None:
  141. context.tag = "initial_sync"
  142. elif full_state:
  143. context.tag = "full_state_sync"
  144. else:
  145. context.tag = "incremental_sync"
  146. if timeout == 0 or since_token is None or full_state:
  147. # we are going to return immediately, so don't bother calling
  148. # notifier.wait_for_events.
  149. result = yield self.current_sync_for_user(
  150. sync_config, since_token, full_state=full_state,
  151. )
  152. defer.returnValue(result)
  153. else:
  154. def current_sync_callback(before_token, after_token):
  155. return self.current_sync_for_user(sync_config, since_token)
  156. result = yield self.notifier.wait_for_events(
  157. sync_config.user.to_string(), timeout, current_sync_callback,
  158. from_token=since_token,
  159. )
  160. defer.returnValue(result)
  161. def current_sync_for_user(self, sync_config, since_token=None,
  162. full_state=False):
  163. """Get the sync for client needed to match what the server has now.
  164. Returns:
  165. A Deferred SyncResult.
  166. """
  167. return self.generate_sync_result(sync_config, since_token, full_state)
  168. @defer.inlineCallbacks
  169. def push_rules_for_user(self, user):
  170. user_id = user.to_string()
  171. rules = yield self.store.get_push_rules_for_user(user_id)
  172. rules = format_push_rules_for_user(user, rules)
  173. defer.returnValue(rules)
  174. @defer.inlineCallbacks
  175. def ephemeral_by_room(self, sync_config, now_token, since_token=None):
  176. """Get the ephemeral events for each room the user is in
  177. Args:
  178. sync_config (SyncConfig): The flags, filters and user for the sync.
  179. now_token (StreamToken): Where the server is currently up to.
  180. since_token (StreamToken): Where the server was when the client
  181. last synced.
  182. Returns:
  183. A tuple of the now StreamToken, updated to reflect the which typing
  184. events are included, and a dict mapping from room_id to a list of
  185. typing events for that room.
  186. """
  187. with Measure(self.clock, "ephemeral_by_room"):
  188. typing_key = since_token.typing_key if since_token else "0"
  189. rooms = yield self.store.get_rooms_for_user(sync_config.user.to_string())
  190. room_ids = [room.room_id for room in rooms]
  191. typing_source = self.event_sources.sources["typing"]
  192. typing, typing_key = yield typing_source.get_new_events(
  193. user=sync_config.user,
  194. from_key=typing_key,
  195. limit=sync_config.filter_collection.ephemeral_limit(),
  196. room_ids=room_ids,
  197. is_guest=sync_config.is_guest,
  198. )
  199. now_token = now_token.copy_and_replace("typing_key", typing_key)
  200. ephemeral_by_room = {}
  201. for event in typing:
  202. # we want to exclude the room_id from the event, but modifying the
  203. # result returned by the event source is poor form (it might cache
  204. # the object)
  205. room_id = event["room_id"]
  206. event_copy = {k: v for (k, v) in event.iteritems()
  207. if k != "room_id"}
  208. ephemeral_by_room.setdefault(room_id, []).append(event_copy)
  209. receipt_key = since_token.receipt_key if since_token else "0"
  210. receipt_source = self.event_sources.sources["receipt"]
  211. receipts, receipt_key = yield receipt_source.get_new_events(
  212. user=sync_config.user,
  213. from_key=receipt_key,
  214. limit=sync_config.filter_collection.ephemeral_limit(),
  215. room_ids=room_ids,
  216. is_guest=sync_config.is_guest,
  217. )
  218. now_token = now_token.copy_and_replace("receipt_key", receipt_key)
  219. for event in receipts:
  220. room_id = event["room_id"]
  221. # exclude room id, as above
  222. event_copy = {k: v for (k, v) in event.iteritems()
  223. if k != "room_id"}
  224. ephemeral_by_room.setdefault(room_id, []).append(event_copy)
  225. defer.returnValue((now_token, ephemeral_by_room))
  226. @defer.inlineCallbacks
  227. def _load_filtered_recents(self, room_id, sync_config, now_token,
  228. since_token=None, recents=None, newly_joined_room=False):
  229. """
  230. Returns:
  231. a Deferred TimelineBatch
  232. """
  233. with Measure(self.clock, "load_filtered_recents"):
  234. timeline_limit = sync_config.filter_collection.timeline_limit()
  235. if recents is None or newly_joined_room or timeline_limit < len(recents):
  236. limited = True
  237. else:
  238. limited = False
  239. if recents:
  240. recents = sync_config.filter_collection.filter_room_timeline(recents)
  241. recents = yield filter_events_for_client(
  242. self.store,
  243. sync_config.user.to_string(),
  244. recents,
  245. )
  246. else:
  247. recents = []
  248. if not limited:
  249. defer.returnValue(TimelineBatch(
  250. events=recents,
  251. prev_batch=now_token,
  252. limited=False
  253. ))
  254. filtering_factor = 2
  255. load_limit = max(timeline_limit * filtering_factor, 10)
  256. max_repeat = 5 # Only try a few times per room, otherwise
  257. room_key = now_token.room_key
  258. end_key = room_key
  259. since_key = None
  260. if since_token and not newly_joined_room:
  261. since_key = since_token.room_key
  262. while limited and len(recents) < timeline_limit and max_repeat:
  263. events, end_key = yield self.store.get_room_events_stream_for_room(
  264. room_id,
  265. limit=load_limit + 1,
  266. from_key=since_key,
  267. to_key=end_key,
  268. )
  269. loaded_recents = sync_config.filter_collection.filter_room_timeline(
  270. events
  271. )
  272. loaded_recents = yield filter_events_for_client(
  273. self.store,
  274. sync_config.user.to_string(),
  275. loaded_recents,
  276. )
  277. loaded_recents.extend(recents)
  278. recents = loaded_recents
  279. if len(events) <= load_limit:
  280. limited = False
  281. break
  282. max_repeat -= 1
  283. if len(recents) > timeline_limit:
  284. limited = True
  285. recents = recents[-timeline_limit:]
  286. room_key = recents[0].internal_metadata.before
  287. prev_batch_token = now_token.copy_and_replace(
  288. "room_key", room_key
  289. )
  290. defer.returnValue(TimelineBatch(
  291. events=recents,
  292. prev_batch=prev_batch_token,
  293. limited=limited or newly_joined_room
  294. ))
  295. @defer.inlineCallbacks
  296. def get_state_after_event(self, event):
  297. """
  298. Get the room state after the given event
  299. Args:
  300. event(synapse.events.EventBase): event of interest
  301. Returns:
  302. A Deferred map from ((type, state_key)->Event)
  303. """
  304. state = yield self.store.get_state_for_event(event.event_id)
  305. if event.is_state():
  306. state = state.copy()
  307. state[(event.type, event.state_key)] = event
  308. defer.returnValue(state)
  309. @defer.inlineCallbacks
  310. def get_state_at(self, room_id, stream_position):
  311. """ Get the room state at a particular stream position
  312. Args:
  313. room_id(str): room for which to get state
  314. stream_position(StreamToken): point at which to get state
  315. Returns:
  316. A Deferred map from ((type, state_key)->Event)
  317. """
  318. last_events, token = yield self.store.get_recent_events_for_room(
  319. room_id, end_token=stream_position.room_key, limit=1,
  320. )
  321. if last_events:
  322. last_event = last_events[-1]
  323. state = yield self.get_state_after_event(last_event)
  324. else:
  325. # no events in this room - so presumably no state
  326. state = {}
  327. defer.returnValue(state)
  328. @defer.inlineCallbacks
  329. def compute_state_delta(self, room_id, batch, sync_config, since_token, now_token,
  330. full_state):
  331. """ Works out the differnce in state between the start of the timeline
  332. and the previous sync.
  333. Args:
  334. room_id(str):
  335. batch(synapse.handlers.sync.TimelineBatch): The timeline batch for
  336. the room that will be sent to the user.
  337. sync_config(synapse.handlers.sync.SyncConfig):
  338. since_token(str|None): Token of the end of the previous batch. May
  339. be None.
  340. now_token(str): Token of the end of the current batch.
  341. full_state(bool): Whether to force returning the full state.
  342. Returns:
  343. A deferred new event dictionary
  344. """
  345. # TODO(mjark) Check if the state events were received by the server
  346. # after the previous sync, since we need to include those state
  347. # updates even if they occured logically before the previous event.
  348. # TODO(mjark) Check for new redactions in the state events.
  349. with Measure(self.clock, "compute_state_delta"):
  350. if full_state:
  351. if batch:
  352. current_state = yield self.store.get_state_for_event(
  353. batch.events[-1].event_id
  354. )
  355. state = yield self.store.get_state_for_event(
  356. batch.events[0].event_id
  357. )
  358. else:
  359. current_state = yield self.get_state_at(
  360. room_id, stream_position=now_token
  361. )
  362. state = current_state
  363. timeline_state = {
  364. (event.type, event.state_key): event
  365. for event in batch.events if event.is_state()
  366. }
  367. state = _calculate_state(
  368. timeline_contains=timeline_state,
  369. timeline_start=state,
  370. previous={},
  371. current=current_state,
  372. )
  373. elif batch.limited:
  374. state_at_previous_sync = yield self.get_state_at(
  375. room_id, stream_position=since_token
  376. )
  377. current_state = yield self.store.get_state_for_event(
  378. batch.events[-1].event_id
  379. )
  380. state_at_timeline_start = yield self.store.get_state_for_event(
  381. batch.events[0].event_id
  382. )
  383. timeline_state = {
  384. (event.type, event.state_key): event
  385. for event in batch.events if event.is_state()
  386. }
  387. state = _calculate_state(
  388. timeline_contains=timeline_state,
  389. timeline_start=state_at_timeline_start,
  390. previous=state_at_previous_sync,
  391. current=current_state,
  392. )
  393. else:
  394. state = {}
  395. defer.returnValue({
  396. (e.type, e.state_key): e
  397. for e in sync_config.filter_collection.filter_room_state(state.values())
  398. })
  399. @defer.inlineCallbacks
  400. def unread_notifs_for_room_id(self, room_id, sync_config):
  401. with Measure(self.clock, "unread_notifs_for_room_id"):
  402. last_unread_event_id = yield self.store.get_last_receipt_event_id_for_user(
  403. user_id=sync_config.user.to_string(),
  404. room_id=room_id,
  405. receipt_type="m.read"
  406. )
  407. notifs = []
  408. if last_unread_event_id:
  409. notifs = yield self.store.get_unread_event_push_actions_by_room_for_user(
  410. room_id, sync_config.user.to_string(), last_unread_event_id
  411. )
  412. defer.returnValue(notifs)
  413. # There is no new information in this period, so your notification
  414. # count is whatever it was last time.
  415. defer.returnValue(None)
  416. @defer.inlineCallbacks
  417. def generate_sync_result(self, sync_config, since_token=None, full_state=False):
  418. """Generates a sync result.
  419. Args:
  420. sync_config (SyncConfig)
  421. since_token (StreamToken)
  422. full_state (bool)
  423. Returns:
  424. Deferred(SyncResult)
  425. """
  426. # NB: The now_token gets changed by some of the generate_sync_* methods,
  427. # this is due to some of the underlying streams not supporting the ability
  428. # to query up to a given point.
  429. # Always use the `now_token` in `SyncResultBuilder`
  430. now_token = yield self.event_sources.get_current_token()
  431. sync_result_builder = SyncResultBuilder(
  432. sync_config, full_state,
  433. since_token=since_token,
  434. now_token=now_token,
  435. )
  436. account_data_by_room = yield self._generate_sync_entry_for_account_data(
  437. sync_result_builder
  438. )
  439. res = yield self._generate_sync_entry_for_rooms(
  440. sync_result_builder, account_data_by_room
  441. )
  442. newly_joined_rooms, newly_joined_users = res
  443. yield self._generate_sync_entry_for_presence(
  444. sync_result_builder, newly_joined_rooms, newly_joined_users
  445. )
  446. defer.returnValue(SyncResult(
  447. presence=sync_result_builder.presence,
  448. account_data=sync_result_builder.account_data,
  449. joined=sync_result_builder.joined,
  450. invited=sync_result_builder.invited,
  451. archived=sync_result_builder.archived,
  452. next_batch=sync_result_builder.now_token,
  453. ))
  454. @defer.inlineCallbacks
  455. def _generate_sync_entry_for_account_data(self, sync_result_builder):
  456. """Generates the account data portion of the sync response. Populates
  457. `sync_result_builder` with the result.
  458. Args:
  459. sync_result_builder(SyncResultBuilder)
  460. Returns:
  461. Deferred(dict): A dictionary containing the per room account data.
  462. """
  463. sync_config = sync_result_builder.sync_config
  464. user_id = sync_result_builder.sync_config.user.to_string()
  465. since_token = sync_result_builder.since_token
  466. if since_token and not sync_result_builder.full_state:
  467. account_data, account_data_by_room = (
  468. yield self.store.get_updated_account_data_for_user(
  469. user_id,
  470. since_token.account_data_key,
  471. )
  472. )
  473. push_rules_changed = yield self.store.have_push_rules_changed_for_user(
  474. user_id, int(since_token.push_rules_key)
  475. )
  476. if push_rules_changed:
  477. account_data["m.push_rules"] = yield self.push_rules_for_user(
  478. sync_config.user
  479. )
  480. else:
  481. account_data, account_data_by_room = (
  482. yield self.store.get_account_data_for_user(
  483. sync_config.user.to_string()
  484. )
  485. )
  486. account_data['m.push_rules'] = yield self.push_rules_for_user(
  487. sync_config.user
  488. )
  489. account_data_for_user = sync_config.filter_collection.filter_account_data([
  490. {"type": account_data_type, "content": content}
  491. for account_data_type, content in account_data.items()
  492. ])
  493. sync_result_builder.account_data = account_data_for_user
  494. defer.returnValue(account_data_by_room)
  495. @defer.inlineCallbacks
  496. def _generate_sync_entry_for_presence(self, sync_result_builder, newly_joined_rooms,
  497. newly_joined_users):
  498. """Generates the presence portion of the sync response. Populates the
  499. `sync_result_builder` with the result.
  500. Args:
  501. sync_result_builder(SyncResultBuilder)
  502. newly_joined_rooms(list): List of rooms that the user has joined
  503. since the last sync (or empty if an initial sync)
  504. newly_joined_users(list): List of users that have joined rooms
  505. since the last sync (or empty if an initial sync)
  506. """
  507. now_token = sync_result_builder.now_token
  508. sync_config = sync_result_builder.sync_config
  509. user = sync_result_builder.sync_config.user
  510. presence_source = self.event_sources.sources["presence"]
  511. since_token = sync_result_builder.since_token
  512. if since_token and not sync_result_builder.full_state:
  513. presence_key = since_token.presence_key
  514. include_offline = True
  515. else:
  516. presence_key = None
  517. include_offline = False
  518. presence, presence_key = yield presence_source.get_new_events(
  519. user=user,
  520. from_key=presence_key,
  521. is_guest=sync_config.is_guest,
  522. include_offline=include_offline,
  523. )
  524. sync_result_builder.now_token = now_token.copy_and_replace(
  525. "presence_key", presence_key
  526. )
  527. extra_users_ids = set(newly_joined_users)
  528. for room_id in newly_joined_rooms:
  529. users = yield self.store.get_users_in_room(room_id)
  530. extra_users_ids.update(users)
  531. extra_users_ids.discard(user.to_string())
  532. states = yield self.presence_handler.get_states(
  533. extra_users_ids,
  534. as_event=True,
  535. )
  536. presence.extend(states)
  537. # Deduplicate the presence entries so that there's at most one per user
  538. presence = {p["content"]["user_id"]: p for p in presence}.values()
  539. presence = sync_config.filter_collection.filter_presence(
  540. presence
  541. )
  542. sync_result_builder.presence = presence
  543. @defer.inlineCallbacks
  544. def _generate_sync_entry_for_rooms(self, sync_result_builder, account_data_by_room):
  545. """Generates the rooms portion of the sync response. Populates the
  546. `sync_result_builder` with the result.
  547. Args:
  548. sync_result_builder(SyncResultBuilder)
  549. account_data_by_room(dict): Dictionary of per room account data
  550. Returns:
  551. Deferred(tuple): Returns a 2-tuple of
  552. `(newly_joined_rooms, newly_joined_users)`
  553. """
  554. user_id = sync_result_builder.sync_config.user.to_string()
  555. now_token, ephemeral_by_room = yield self.ephemeral_by_room(
  556. sync_result_builder.sync_config,
  557. now_token=sync_result_builder.now_token,
  558. since_token=sync_result_builder.since_token,
  559. )
  560. sync_result_builder.now_token = now_token
  561. ignored_account_data = yield self.store.get_global_account_data_by_type_for_user(
  562. "m.ignored_user_list", user_id=user_id,
  563. )
  564. if ignored_account_data:
  565. ignored_users = ignored_account_data.get("ignored_users", {}).keys()
  566. else:
  567. ignored_users = frozenset()
  568. if sync_result_builder.since_token:
  569. res = yield self._get_rooms_changed(sync_result_builder, ignored_users)
  570. room_entries, invited, newly_joined_rooms = res
  571. tags_by_room = yield self.store.get_updated_tags(
  572. user_id,
  573. sync_result_builder.since_token.account_data_key,
  574. )
  575. else:
  576. res = yield self._get_all_rooms(sync_result_builder, ignored_users)
  577. room_entries, invited, newly_joined_rooms = res
  578. tags_by_room = yield self.store.get_tags_for_user(user_id)
  579. def handle_room_entries(room_entry):
  580. return self._generate_room_entry(
  581. sync_result_builder,
  582. ignored_users,
  583. room_entry,
  584. ephemeral=ephemeral_by_room.get(room_entry.room_id, []),
  585. tags=tags_by_room.get(room_entry.room_id),
  586. account_data=account_data_by_room.get(room_entry.room_id, {}),
  587. always_include=sync_result_builder.full_state,
  588. )
  589. yield concurrently_execute(handle_room_entries, room_entries, 10)
  590. sync_result_builder.invited.extend(invited)
  591. # Now we want to get any newly joined users
  592. newly_joined_users = set()
  593. if sync_result_builder.since_token:
  594. for joined_sync in sync_result_builder.joined:
  595. it = itertools.chain(
  596. joined_sync.timeline.events, joined_sync.state.values()
  597. )
  598. for event in it:
  599. if event.type == EventTypes.Member:
  600. if event.membership == Membership.JOIN:
  601. newly_joined_users.add(event.state_key)
  602. defer.returnValue((newly_joined_rooms, newly_joined_users))
  603. @defer.inlineCallbacks
  604. def _get_rooms_changed(self, sync_result_builder, ignored_users):
  605. """Gets the the changes that have happened since the last sync.
  606. Args:
  607. sync_result_builder(SyncResultBuilder)
  608. ignored_users(set(str)): Set of users ignored by user.
  609. Returns:
  610. Deferred(tuple): Returns a tuple of the form:
  611. `([RoomSyncResultBuilder], [InvitedSyncResult], newly_joined_rooms)`
  612. """
  613. user_id = sync_result_builder.sync_config.user.to_string()
  614. since_token = sync_result_builder.since_token
  615. now_token = sync_result_builder.now_token
  616. sync_config = sync_result_builder.sync_config
  617. assert since_token
  618. app_service = yield self.store.get_app_service_by_user_id(user_id)
  619. if app_service:
  620. rooms = yield self.store.get_app_service_rooms(app_service)
  621. joined_room_ids = set(r.room_id for r in rooms)
  622. else:
  623. rooms = yield self.store.get_rooms_for_user(user_id)
  624. joined_room_ids = set(r.room_id for r in rooms)
  625. # Get a list of membership change events that have happened.
  626. rooms_changed = yield self.store.get_membership_changes_for_user(
  627. user_id, since_token.room_key, now_token.room_key
  628. )
  629. mem_change_events_by_room_id = {}
  630. for event in rooms_changed:
  631. mem_change_events_by_room_id.setdefault(event.room_id, []).append(event)
  632. newly_joined_rooms = []
  633. room_entries = []
  634. invited = []
  635. for room_id, events in mem_change_events_by_room_id.items():
  636. non_joins = [e for e in events if e.membership != Membership.JOIN]
  637. has_join = len(non_joins) != len(events)
  638. # We want to figure out if we joined the room at some point since
  639. # the last sync (even if we have since left). This is to make sure
  640. # we do send down the room, and with full state, where necessary
  641. if room_id in joined_room_ids or has_join:
  642. old_state = yield self.get_state_at(room_id, since_token)
  643. old_mem_ev = old_state.get((EventTypes.Member, user_id), None)
  644. if not old_mem_ev or old_mem_ev.membership != Membership.JOIN:
  645. newly_joined_rooms.append(room_id)
  646. if room_id in joined_room_ids:
  647. continue
  648. if not non_joins:
  649. continue
  650. # Only bother if we're still currently invited
  651. should_invite = non_joins[-1].membership == Membership.INVITE
  652. if should_invite:
  653. if event.sender not in ignored_users:
  654. room_sync = InvitedSyncResult(room_id, invite=non_joins[-1])
  655. if room_sync:
  656. invited.append(room_sync)
  657. # Always include leave/ban events. Just take the last one.
  658. # TODO: How do we handle ban -> leave in same batch?
  659. leave_events = [
  660. e for e in non_joins
  661. if e.membership in (Membership.LEAVE, Membership.BAN)
  662. ]
  663. if leave_events:
  664. leave_event = leave_events[-1]
  665. leave_stream_token = yield self.store.get_stream_token_for_event(
  666. leave_event.event_id
  667. )
  668. leave_token = since_token.copy_and_replace(
  669. "room_key", leave_stream_token
  670. )
  671. if since_token and since_token.is_after(leave_token):
  672. continue
  673. room_entries.append(RoomSyncResultBuilder(
  674. room_id=room_id,
  675. rtype="archived",
  676. events=None,
  677. newly_joined=room_id in newly_joined_rooms,
  678. full_state=False,
  679. since_token=since_token,
  680. upto_token=leave_token,
  681. ))
  682. timeline_limit = sync_config.filter_collection.timeline_limit()
  683. # Get all events for rooms we're currently joined to.
  684. room_to_events = yield self.store.get_room_events_stream_for_rooms(
  685. room_ids=joined_room_ids,
  686. from_key=since_token.room_key,
  687. to_key=now_token.room_key,
  688. limit=timeline_limit + 1,
  689. )
  690. # We loop through all room ids, even if there are no new events, in case
  691. # there are non room events taht we need to notify about.
  692. for room_id in joined_room_ids:
  693. room_entry = room_to_events.get(room_id, None)
  694. if room_entry:
  695. events, start_key = room_entry
  696. prev_batch_token = now_token.copy_and_replace("room_key", start_key)
  697. room_entries.append(RoomSyncResultBuilder(
  698. room_id=room_id,
  699. rtype="joined",
  700. events=events,
  701. newly_joined=room_id in newly_joined_rooms,
  702. full_state=False,
  703. since_token=None if room_id in newly_joined_rooms else since_token,
  704. upto_token=prev_batch_token,
  705. ))
  706. else:
  707. room_entries.append(RoomSyncResultBuilder(
  708. room_id=room_id,
  709. rtype="joined",
  710. events=[],
  711. newly_joined=room_id in newly_joined_rooms,
  712. full_state=False,
  713. since_token=since_token,
  714. upto_token=since_token,
  715. ))
  716. defer.returnValue((room_entries, invited, newly_joined_rooms))
  717. @defer.inlineCallbacks
  718. def _get_all_rooms(self, sync_result_builder, ignored_users):
  719. """Returns entries for all rooms for the user.
  720. Args:
  721. sync_result_builder(SyncResultBuilder)
  722. ignored_users(set(str)): Set of users ignored by user.
  723. Returns:
  724. Deferred(tuple): Returns a tuple of the form:
  725. `([RoomSyncResultBuilder], [InvitedSyncResult], [])`
  726. """
  727. user_id = sync_result_builder.sync_config.user.to_string()
  728. since_token = sync_result_builder.since_token
  729. now_token = sync_result_builder.now_token
  730. sync_config = sync_result_builder.sync_config
  731. membership_list = (
  732. Membership.INVITE, Membership.JOIN, Membership.LEAVE, Membership.BAN
  733. )
  734. room_list = yield self.store.get_rooms_for_user_where_membership_is(
  735. user_id=user_id,
  736. membership_list=membership_list
  737. )
  738. room_entries = []
  739. invited = []
  740. for event in room_list:
  741. if event.membership == Membership.JOIN:
  742. room_entries.append(RoomSyncResultBuilder(
  743. room_id=event.room_id,
  744. rtype="joined",
  745. events=None,
  746. newly_joined=False,
  747. full_state=True,
  748. since_token=since_token,
  749. upto_token=now_token,
  750. ))
  751. elif event.membership == Membership.INVITE:
  752. if event.sender in ignored_users:
  753. continue
  754. invite = yield self.store.get_event(event.event_id)
  755. invited.append(InvitedSyncResult(
  756. room_id=event.room_id,
  757. invite=invite,
  758. ))
  759. elif event.membership in (Membership.LEAVE, Membership.BAN):
  760. # Always send down rooms we were banned or kicked from.
  761. if not sync_config.filter_collection.include_leave:
  762. if event.membership == Membership.LEAVE:
  763. if user_id == event.sender:
  764. continue
  765. leave_token = now_token.copy_and_replace(
  766. "room_key", "s%d" % (event.stream_ordering,)
  767. )
  768. room_entries.append(RoomSyncResultBuilder(
  769. room_id=event.room_id,
  770. rtype="archived",
  771. events=None,
  772. newly_joined=False,
  773. full_state=True,
  774. since_token=since_token,
  775. upto_token=leave_token,
  776. ))
  777. defer.returnValue((room_entries, invited, []))
  778. @defer.inlineCallbacks
  779. def _generate_room_entry(self, sync_result_builder, ignored_users,
  780. room_builder, ephemeral, tags, account_data,
  781. always_include=False):
  782. """Populates the `joined` and `archived` section of `sync_result_builder`
  783. based on the `room_builder`.
  784. Args:
  785. sync_result_builder(SyncResultBuilder)
  786. ignored_users(set(str)): Set of users ignored by user.
  787. room_builder(RoomSyncResultBuilder)
  788. ephemeral(list): List of new ephemeral events for room
  789. tags(list): List of *all* tags for room, or None if there has been
  790. no change.
  791. account_data(list): List of new account data for room
  792. always_include(bool): Always include this room in the sync response,
  793. even if empty.
  794. """
  795. newly_joined = room_builder.newly_joined
  796. full_state = (
  797. room_builder.full_state
  798. or newly_joined
  799. or sync_result_builder.full_state
  800. )
  801. events = room_builder.events
  802. # We want to shortcut out as early as possible.
  803. if not (always_include or account_data or ephemeral or full_state):
  804. if events == [] and tags is None:
  805. return
  806. since_token = sync_result_builder.since_token
  807. now_token = sync_result_builder.now_token
  808. sync_config = sync_result_builder.sync_config
  809. room_id = room_builder.room_id
  810. since_token = room_builder.since_token
  811. upto_token = room_builder.upto_token
  812. batch = yield self._load_filtered_recents(
  813. room_id, sync_config,
  814. now_token=upto_token,
  815. since_token=since_token,
  816. recents=events,
  817. newly_joined_room=newly_joined,
  818. )
  819. account_data_events = []
  820. if tags is not None:
  821. account_data_events.append({
  822. "type": "m.tag",
  823. "content": {"tags": tags},
  824. })
  825. for account_data_type, content in account_data.items():
  826. account_data_events.append({
  827. "type": account_data_type,
  828. "content": content,
  829. })
  830. account_data = sync_config.filter_collection.filter_room_account_data(
  831. account_data_events
  832. )
  833. ephemeral = sync_config.filter_collection.filter_room_ephemeral(ephemeral)
  834. if not (always_include or batch or account_data or ephemeral or full_state):
  835. return
  836. state = yield self.compute_state_delta(
  837. room_id, batch, sync_config, since_token, now_token,
  838. full_state=full_state
  839. )
  840. if room_builder.rtype == "joined":
  841. unread_notifications = {}
  842. room_sync = JoinedSyncResult(
  843. room_id=room_id,
  844. timeline=batch,
  845. state=state,
  846. ephemeral=ephemeral,
  847. account_data=account_data_events,
  848. unread_notifications=unread_notifications,
  849. )
  850. if room_sync or always_include:
  851. notifs = yield self.unread_notifs_for_room_id(
  852. room_id, sync_config
  853. )
  854. if notifs is not None:
  855. unread_notifications["notification_count"] = notifs["notify_count"]
  856. unread_notifications["highlight_count"] = notifs["highlight_count"]
  857. sync_result_builder.joined.append(room_sync)
  858. elif room_builder.rtype == "archived":
  859. room_sync = ArchivedSyncResult(
  860. room_id=room_id,
  861. timeline=batch,
  862. state=state,
  863. account_data=account_data,
  864. )
  865. if room_sync or always_include:
  866. sync_result_builder.archived.append(room_sync)
  867. else:
  868. raise Exception("Unrecognized rtype: %r", room_builder.rtype)
  869. def _action_has_highlight(actions):
  870. for action in actions:
  871. try:
  872. if action.get("set_tweak", None) == "highlight":
  873. return action.get("value", True)
  874. except AttributeError:
  875. pass
  876. return False
  877. def _calculate_state(timeline_contains, timeline_start, previous, current):
  878. """Works out what state to include in a sync response.
  879. Args:
  880. timeline_contains (dict): state in the timeline
  881. timeline_start (dict): state at the start of the timeline
  882. previous (dict): state at the end of the previous sync (or empty dict
  883. if this is an initial sync)
  884. current (dict): state at the end of the timeline
  885. Returns:
  886. dict
  887. """
  888. event_id_to_state = {
  889. e.event_id: e
  890. for e in itertools.chain(
  891. timeline_contains.values(),
  892. previous.values(),
  893. timeline_start.values(),
  894. current.values(),
  895. )
  896. }
  897. c_ids = set(e.event_id for e in current.values())
  898. tc_ids = set(e.event_id for e in timeline_contains.values())
  899. p_ids = set(e.event_id for e in previous.values())
  900. ts_ids = set(e.event_id for e in timeline_start.values())
  901. state_ids = ((c_ids | ts_ids) - p_ids) - tc_ids
  902. evs = (event_id_to_state[e] for e in state_ids)
  903. return {
  904. (e.type, e.state_key): e
  905. for e in evs
  906. }
  907. class SyncResultBuilder(object):
  908. "Used to help build up a new SyncResult for a user"
  909. def __init__(self, sync_config, full_state, since_token, now_token):
  910. """
  911. Args:
  912. sync_config(SyncConfig)
  913. full_state(bool): The full_state flag as specified by user
  914. since_token(StreamToken): The token supplied by user, or None.
  915. now_token(StreamToken): The token to sync up to.
  916. """
  917. self.sync_config = sync_config
  918. self.full_state = full_state
  919. self.since_token = since_token
  920. self.now_token = now_token
  921. self.presence = []
  922. self.account_data = []
  923. self.joined = []
  924. self.invited = []
  925. self.archived = []
  926. class RoomSyncResultBuilder(object):
  927. """Stores information needed to create either a `JoinedSyncResult` or
  928. `ArchivedSyncResult`.
  929. """
  930. def __init__(self, room_id, rtype, events, newly_joined, full_state,
  931. since_token, upto_token):
  932. """
  933. Args:
  934. room_id(str)
  935. rtype(str): One of `"joined"` or `"archived"`
  936. events(list): List of events to include in the room, (more events
  937. may be added when generating result).
  938. newly_joined(bool): If the user has newly joined the room
  939. full_state(bool): Whether the full state should be sent in result
  940. since_token(StreamToken): Earliest point to return events from, or None
  941. upto_token(StreamToken): Latest point to return events from.
  942. """
  943. self.room_id = room_id
  944. self.rtype = rtype
  945. self.events = events
  946. self.newly_joined = newly_joined
  947. self.full_state = full_state
  948. self.since_token = since_token
  949. self.upto_token = upto_token