roommember.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-2016 OpenMarket Ltd
  3. # Copyright 2018 New Vector Ltd
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. from twisted.internet import defer
  17. from collections import namedtuple
  18. from synapse.storage.events import EventsWorkerStore
  19. from synapse.util.async import Linearizer
  20. from synapse.util.caches import intern_string
  21. from synapse.util.caches.descriptors import cached, cachedInlineCallbacks
  22. from synapse.util.stringutils import to_ascii
  23. from synapse.api.constants import Membership, EventTypes
  24. from synapse.types import get_domain_from_id
  25. import logging
  26. import simplejson as json
  27. logger = logging.getLogger(__name__)
  28. RoomsForUser = namedtuple(
  29. "RoomsForUser",
  30. ("room_id", "sender", "membership", "event_id", "stream_ordering")
  31. )
  32. GetRoomsForUserWithStreamOrdering = namedtuple(
  33. "_GetRoomsForUserWithStreamOrdering",
  34. ("room_id", "stream_ordering",)
  35. )
  36. # We store this using a namedtuple so that we save about 3x space over using a
  37. # dict.
  38. ProfileInfo = namedtuple(
  39. "ProfileInfo", ("avatar_url", "display_name")
  40. )
  41. _MEMBERSHIP_PROFILE_UPDATE_NAME = "room_membership_profile_update"
  42. class RoomMemberWorkerStore(EventsWorkerStore):
  43. @cachedInlineCallbacks(max_entries=100000, iterable=True, cache_context=True)
  44. def get_hosts_in_room(self, room_id, cache_context):
  45. """Returns the set of all hosts currently in the room
  46. """
  47. user_ids = yield self.get_users_in_room(
  48. room_id, on_invalidate=cache_context.invalidate,
  49. )
  50. hosts = frozenset(get_domain_from_id(user_id) for user_id in user_ids)
  51. defer.returnValue(hosts)
  52. @cached(max_entries=100000, iterable=True)
  53. def get_users_in_room(self, room_id):
  54. def f(txn):
  55. sql = (
  56. "SELECT m.user_id FROM room_memberships as m"
  57. " INNER JOIN current_state_events as c"
  58. " ON m.event_id = c.event_id "
  59. " AND m.room_id = c.room_id "
  60. " AND m.user_id = c.state_key"
  61. " WHERE c.type = 'm.room.member' AND c.room_id = ? AND m.membership = ?"
  62. )
  63. txn.execute(sql, (room_id, Membership.JOIN,))
  64. return [to_ascii(r[0]) for r in txn]
  65. return self.runInteraction("get_users_in_room", f)
  66. @cached()
  67. def get_invited_rooms_for_user(self, user_id):
  68. """ Get all the rooms the user is invited to
  69. Args:
  70. user_id (str): The user ID.
  71. Returns:
  72. A deferred list of RoomsForUser.
  73. """
  74. return self.get_rooms_for_user_where_membership_is(
  75. user_id, [Membership.INVITE]
  76. )
  77. @defer.inlineCallbacks
  78. def get_invite_for_user_in_room(self, user_id, room_id):
  79. """Gets the invite for the given user and room
  80. Args:
  81. user_id (str)
  82. room_id (str)
  83. Returns:
  84. Deferred: Resolves to either a RoomsForUser or None if no invite was
  85. found.
  86. """
  87. invites = yield self.get_invited_rooms_for_user(user_id)
  88. for invite in invites:
  89. if invite.room_id == room_id:
  90. defer.returnValue(invite)
  91. defer.returnValue(None)
  92. def get_rooms_for_user_where_membership_is(self, user_id, membership_list):
  93. """ Get all the rooms for this user where the membership for this user
  94. matches one in the membership list.
  95. Args:
  96. user_id (str): The user ID.
  97. membership_list (list): A list of synapse.api.constants.Membership
  98. values which the user must be in.
  99. Returns:
  100. A list of dictionary objects, with room_id, membership and sender
  101. defined.
  102. """
  103. if not membership_list:
  104. return defer.succeed(None)
  105. return self.runInteraction(
  106. "get_rooms_for_user_where_membership_is",
  107. self._get_rooms_for_user_where_membership_is_txn,
  108. user_id, membership_list
  109. )
  110. def _get_rooms_for_user_where_membership_is_txn(self, txn, user_id,
  111. membership_list):
  112. do_invite = Membership.INVITE in membership_list
  113. membership_list = [m for m in membership_list if m != Membership.INVITE]
  114. results = []
  115. if membership_list:
  116. where_clause = "user_id = ? AND (%s) AND forgotten = 0" % (
  117. " OR ".join(["membership = ?" for _ in membership_list]),
  118. )
  119. args = [user_id]
  120. args.extend(membership_list)
  121. sql = (
  122. "SELECT m.room_id, m.sender, m.membership, m.event_id, e.stream_ordering"
  123. " FROM current_state_events as c"
  124. " INNER JOIN room_memberships as m"
  125. " ON m.event_id = c.event_id"
  126. " INNER JOIN events as e"
  127. " ON e.event_id = c.event_id"
  128. " AND m.room_id = c.room_id"
  129. " AND m.user_id = c.state_key"
  130. " WHERE c.type = 'm.room.member' AND %s"
  131. ) % (where_clause,)
  132. txn.execute(sql, args)
  133. results = [
  134. RoomsForUser(**r) for r in self.cursor_to_dict(txn)
  135. ]
  136. if do_invite:
  137. sql = (
  138. "SELECT i.room_id, inviter, i.event_id, e.stream_ordering"
  139. " FROM local_invites as i"
  140. " INNER JOIN events as e USING (event_id)"
  141. " WHERE invitee = ? AND locally_rejected is NULL"
  142. " AND replaced_by is NULL"
  143. )
  144. txn.execute(sql, (user_id,))
  145. results.extend(RoomsForUser(
  146. room_id=r["room_id"],
  147. sender=r["inviter"],
  148. event_id=r["event_id"],
  149. stream_ordering=r["stream_ordering"],
  150. membership=Membership.INVITE,
  151. ) for r in self.cursor_to_dict(txn))
  152. return results
  153. @cachedInlineCallbacks(max_entries=500000, iterable=True)
  154. def get_rooms_for_user_with_stream_ordering(self, user_id):
  155. """Returns a set of room_ids the user is currently joined to
  156. Args:
  157. user_id (str)
  158. Returns:
  159. Deferred[frozenset[GetRoomsForUserWithStreamOrdering]]: Returns
  160. the rooms the user is in currently, along with the stream ordering
  161. of the most recent join for that user and room.
  162. """
  163. rooms = yield self.get_rooms_for_user_where_membership_is(
  164. user_id, membership_list=[Membership.JOIN],
  165. )
  166. defer.returnValue(frozenset(
  167. GetRoomsForUserWithStreamOrdering(r.room_id, r.stream_ordering)
  168. for r in rooms
  169. ))
  170. @defer.inlineCallbacks
  171. def get_rooms_for_user(self, user_id, on_invalidate=None):
  172. """Returns a set of room_ids the user is currently joined to
  173. """
  174. rooms = yield self.get_rooms_for_user_with_stream_ordering(
  175. user_id, on_invalidate=on_invalidate,
  176. )
  177. defer.returnValue(frozenset(r.room_id for r in rooms))
  178. @cachedInlineCallbacks(max_entries=500000, cache_context=True, iterable=True)
  179. def get_users_who_share_room_with_user(self, user_id, cache_context):
  180. """Returns the set of users who share a room with `user_id`
  181. """
  182. room_ids = yield self.get_rooms_for_user(
  183. user_id, on_invalidate=cache_context.invalidate,
  184. )
  185. user_who_share_room = set()
  186. for room_id in room_ids:
  187. user_ids = yield self.get_users_in_room(
  188. room_id, on_invalidate=cache_context.invalidate,
  189. )
  190. user_who_share_room.update(user_ids)
  191. defer.returnValue(user_who_share_room)
  192. def get_joined_users_from_context(self, event, context):
  193. state_group = context.state_group
  194. if not state_group:
  195. # If state_group is None it means it has yet to be assigned a
  196. # state group, i.e. we need to make sure that calls with a state_group
  197. # of None don't hit previous cached calls with a None state_group.
  198. # To do this we set the state_group to a new object as object() != object()
  199. state_group = object()
  200. return self._get_joined_users_from_context(
  201. event.room_id, state_group, context.current_state_ids,
  202. event=event,
  203. context=context,
  204. )
  205. def get_joined_users_from_state(self, room_id, state_entry):
  206. state_group = state_entry.state_group
  207. if not state_group:
  208. # If state_group is None it means it has yet to be assigned a
  209. # state group, i.e. we need to make sure that calls with a state_group
  210. # of None don't hit previous cached calls with a None state_group.
  211. # To do this we set the state_group to a new object as object() != object()
  212. state_group = object()
  213. return self._get_joined_users_from_context(
  214. room_id, state_group, state_entry.state, context=state_entry,
  215. )
  216. @cachedInlineCallbacks(num_args=2, cache_context=True, iterable=True,
  217. max_entries=100000)
  218. def _get_joined_users_from_context(self, room_id, state_group, current_state_ids,
  219. cache_context, event=None, context=None):
  220. # We don't use `state_group`, it's there so that we can cache based
  221. # on it. However, it's important that it's never None, since two current_states
  222. # with a state_group of None are likely to be different.
  223. # See bulk_get_push_rules_for_room for how we work around this.
  224. assert state_group is not None
  225. users_in_room = {}
  226. member_event_ids = [
  227. e_id
  228. for key, e_id in current_state_ids.iteritems()
  229. if key[0] == EventTypes.Member
  230. ]
  231. if context is not None:
  232. # If we have a context with a delta from a previous state group,
  233. # check if we also have the result from the previous group in cache.
  234. # If we do then we can reuse that result and simply update it with
  235. # any membership changes in `delta_ids`
  236. if context.prev_group and context.delta_ids:
  237. prev_res = self._get_joined_users_from_context.cache.get(
  238. (room_id, context.prev_group), None
  239. )
  240. if prev_res and isinstance(prev_res, dict):
  241. users_in_room = dict(prev_res)
  242. member_event_ids = [
  243. e_id
  244. for key, e_id in context.delta_ids.iteritems()
  245. if key[0] == EventTypes.Member
  246. ]
  247. for etype, state_key in context.delta_ids:
  248. users_in_room.pop(state_key, None)
  249. # We check if we have any of the member event ids in the event cache
  250. # before we ask the DB
  251. # We don't update the event cache hit ratio as it completely throws off
  252. # the hit ratio counts. After all, we don't populate the cache if we
  253. # miss it here
  254. event_map = self._get_events_from_cache(
  255. member_event_ids,
  256. allow_rejected=False,
  257. update_metrics=False,
  258. )
  259. missing_member_event_ids = []
  260. for event_id in member_event_ids:
  261. ev_entry = event_map.get(event_id)
  262. if ev_entry:
  263. if ev_entry.event.membership == Membership.JOIN:
  264. users_in_room[to_ascii(ev_entry.event.state_key)] = ProfileInfo(
  265. display_name=to_ascii(
  266. ev_entry.event.content.get("displayname", None)
  267. ),
  268. avatar_url=to_ascii(
  269. ev_entry.event.content.get("avatar_url", None)
  270. ),
  271. )
  272. else:
  273. missing_member_event_ids.append(event_id)
  274. if missing_member_event_ids:
  275. rows = yield self._simple_select_many_batch(
  276. table="room_memberships",
  277. column="event_id",
  278. iterable=missing_member_event_ids,
  279. retcols=('user_id', 'display_name', 'avatar_url',),
  280. keyvalues={
  281. "membership": Membership.JOIN,
  282. },
  283. batch_size=500,
  284. desc="_get_joined_users_from_context",
  285. )
  286. users_in_room.update({
  287. to_ascii(row["user_id"]): ProfileInfo(
  288. avatar_url=to_ascii(row["avatar_url"]),
  289. display_name=to_ascii(row["display_name"]),
  290. )
  291. for row in rows
  292. })
  293. if event is not None and event.type == EventTypes.Member:
  294. if event.membership == Membership.JOIN:
  295. if event.event_id in member_event_ids:
  296. users_in_room[to_ascii(event.state_key)] = ProfileInfo(
  297. display_name=to_ascii(event.content.get("displayname", None)),
  298. avatar_url=to_ascii(event.content.get("avatar_url", None)),
  299. )
  300. defer.returnValue(users_in_room)
  301. @cachedInlineCallbacks(max_entries=10000)
  302. def is_host_joined(self, room_id, host):
  303. if '%' in host or '_' in host:
  304. raise Exception("Invalid host name")
  305. sql = """
  306. SELECT state_key FROM current_state_events AS c
  307. INNER JOIN room_memberships USING (event_id)
  308. WHERE membership = 'join'
  309. AND type = 'm.room.member'
  310. AND c.room_id = ?
  311. AND state_key LIKE ?
  312. LIMIT 1
  313. """
  314. # We do need to be careful to ensure that host doesn't have any wild cards
  315. # in it, but we checked above for known ones and we'll check below that
  316. # the returned user actually has the correct domain.
  317. like_clause = "%:" + host
  318. rows = yield self._execute("is_host_joined", None, sql, room_id, like_clause)
  319. if not rows:
  320. defer.returnValue(False)
  321. user_id = rows[0][0]
  322. if get_domain_from_id(user_id) != host:
  323. # This can only happen if the host name has something funky in it
  324. raise Exception("Invalid host name")
  325. defer.returnValue(True)
  326. @cachedInlineCallbacks()
  327. def was_host_joined(self, room_id, host):
  328. """Check whether the server is or ever was in the room.
  329. Args:
  330. room_id (str)
  331. host (str)
  332. Returns:
  333. Deferred: Resolves to True if the host is/was in the room, otherwise
  334. False.
  335. """
  336. if '%' in host or '_' in host:
  337. raise Exception("Invalid host name")
  338. sql = """
  339. SELECT user_id FROM room_memberships
  340. WHERE room_id = ?
  341. AND user_id LIKE ?
  342. AND membership = 'join'
  343. LIMIT 1
  344. """
  345. # We do need to be careful to ensure that host doesn't have any wild cards
  346. # in it, but we checked above for known ones and we'll check below that
  347. # the returned user actually has the correct domain.
  348. like_clause = "%:" + host
  349. rows = yield self._execute("was_host_joined", None, sql, room_id, like_clause)
  350. if not rows:
  351. defer.returnValue(False)
  352. user_id = rows[0][0]
  353. if get_domain_from_id(user_id) != host:
  354. # This can only happen if the host name has something funky in it
  355. raise Exception("Invalid host name")
  356. defer.returnValue(True)
  357. def get_joined_hosts(self, room_id, state_entry):
  358. state_group = state_entry.state_group
  359. if not state_group:
  360. # If state_group is None it means it has yet to be assigned a
  361. # state group, i.e. we need to make sure that calls with a state_group
  362. # of None don't hit previous cached calls with a None state_group.
  363. # To do this we set the state_group to a new object as object() != object()
  364. state_group = object()
  365. return self._get_joined_hosts(
  366. room_id, state_group, state_entry.state, state_entry=state_entry,
  367. )
  368. @cachedInlineCallbacks(num_args=2, max_entries=10000, iterable=True)
  369. # @defer.inlineCallbacks
  370. def _get_joined_hosts(self, room_id, state_group, current_state_ids, state_entry):
  371. # We don't use `state_group`, its there so that we can cache based
  372. # on it. However, its important that its never None, since two current_state's
  373. # with a state_group of None are likely to be different.
  374. # See bulk_get_push_rules_for_room for how we work around this.
  375. assert state_group is not None
  376. cache = self._get_joined_hosts_cache(room_id)
  377. joined_hosts = yield cache.get_destinations(state_entry)
  378. defer.returnValue(joined_hosts)
  379. @cached(max_entries=10000, iterable=True)
  380. def _get_joined_hosts_cache(self, room_id):
  381. return _JoinedHostsCache(self, room_id)
  382. @cached()
  383. def who_forgot_in_room(self, room_id):
  384. return self._simple_select_list(
  385. table="room_memberships",
  386. retcols=("user_id", "event_id"),
  387. keyvalues={
  388. "room_id": room_id,
  389. "forgotten": 1,
  390. },
  391. desc="who_forgot"
  392. )
  393. class RoomMemberStore(RoomMemberWorkerStore):
  394. def __init__(self, db_conn, hs):
  395. super(RoomMemberStore, self).__init__(db_conn, hs)
  396. self.register_background_update_handler(
  397. _MEMBERSHIP_PROFILE_UPDATE_NAME, self._background_add_membership_profile
  398. )
  399. def _store_room_members_txn(self, txn, events, backfilled):
  400. """Store a room member in the database.
  401. """
  402. self._simple_insert_many_txn(
  403. txn,
  404. table="room_memberships",
  405. values=[
  406. {
  407. "event_id": event.event_id,
  408. "user_id": event.state_key,
  409. "sender": event.user_id,
  410. "room_id": event.room_id,
  411. "membership": event.membership,
  412. "display_name": event.content.get("displayname", None),
  413. "avatar_url": event.content.get("avatar_url", None),
  414. }
  415. for event in events
  416. ]
  417. )
  418. for event in events:
  419. txn.call_after(
  420. self._membership_stream_cache.entity_has_changed,
  421. event.state_key, event.internal_metadata.stream_ordering
  422. )
  423. txn.call_after(
  424. self.get_invited_rooms_for_user.invalidate, (event.state_key,)
  425. )
  426. # We update the local_invites table only if the event is "current",
  427. # i.e., its something that has just happened.
  428. # The only current event that can also be an outlier is if its an
  429. # invite that has come in across federation.
  430. is_new_state = not backfilled and (
  431. not event.internal_metadata.is_outlier()
  432. or event.internal_metadata.is_invite_from_remote()
  433. )
  434. is_mine = self.hs.is_mine_id(event.state_key)
  435. if is_new_state and is_mine:
  436. if event.membership == Membership.INVITE:
  437. self._simple_insert_txn(
  438. txn,
  439. table="local_invites",
  440. values={
  441. "event_id": event.event_id,
  442. "invitee": event.state_key,
  443. "inviter": event.sender,
  444. "room_id": event.room_id,
  445. "stream_id": event.internal_metadata.stream_ordering,
  446. }
  447. )
  448. else:
  449. sql = (
  450. "UPDATE local_invites SET stream_id = ?, replaced_by = ? WHERE"
  451. " room_id = ? AND invitee = ? AND locally_rejected is NULL"
  452. " AND replaced_by is NULL"
  453. )
  454. txn.execute(sql, (
  455. event.internal_metadata.stream_ordering,
  456. event.event_id,
  457. event.room_id,
  458. event.state_key,
  459. ))
  460. @defer.inlineCallbacks
  461. def locally_reject_invite(self, user_id, room_id):
  462. sql = (
  463. "UPDATE local_invites SET stream_id = ?, locally_rejected = ? WHERE"
  464. " room_id = ? AND invitee = ? AND locally_rejected is NULL"
  465. " AND replaced_by is NULL"
  466. )
  467. def f(txn, stream_ordering):
  468. txn.execute(sql, (
  469. stream_ordering,
  470. True,
  471. room_id,
  472. user_id,
  473. ))
  474. with self._stream_id_gen.get_next() as stream_ordering:
  475. yield self.runInteraction("locally_reject_invite", f, stream_ordering)
  476. def forget(self, user_id, room_id):
  477. """Indicate that user_id wishes to discard history for room_id."""
  478. def f(txn):
  479. sql = (
  480. "UPDATE"
  481. " room_memberships"
  482. " SET"
  483. " forgotten = 1"
  484. " WHERE"
  485. " user_id = ?"
  486. " AND"
  487. " room_id = ?"
  488. )
  489. txn.execute(sql, (user_id, room_id))
  490. txn.call_after(self.was_forgotten_at.invalidate_all)
  491. txn.call_after(self.did_forget.invalidate, (user_id, room_id))
  492. self._invalidate_cache_and_stream(
  493. txn, self.who_forgot_in_room, (room_id,)
  494. )
  495. return self.runInteraction("forget_membership", f)
  496. @cachedInlineCallbacks(num_args=2)
  497. def did_forget(self, user_id, room_id):
  498. """Returns whether user_id has elected to discard history for room_id.
  499. Returns False if they have since re-joined."""
  500. def f(txn):
  501. sql = (
  502. "SELECT"
  503. " COUNT(*)"
  504. " FROM"
  505. " room_memberships"
  506. " WHERE"
  507. " user_id = ?"
  508. " AND"
  509. " room_id = ?"
  510. " AND"
  511. " forgotten = 0"
  512. )
  513. txn.execute(sql, (user_id, room_id))
  514. rows = txn.fetchall()
  515. return rows[0][0]
  516. count = yield self.runInteraction("did_forget_membership", f)
  517. defer.returnValue(count == 0)
  518. @cachedInlineCallbacks(num_args=3)
  519. def was_forgotten_at(self, user_id, room_id, event_id):
  520. """Returns whether user_id has elected to discard history for room_id at
  521. event_id.
  522. event_id must be a membership event."""
  523. def f(txn):
  524. sql = (
  525. "SELECT"
  526. " forgotten"
  527. " FROM"
  528. " room_memberships"
  529. " WHERE"
  530. " user_id = ?"
  531. " AND"
  532. " room_id = ?"
  533. " AND"
  534. " event_id = ?"
  535. )
  536. txn.execute(sql, (user_id, room_id, event_id))
  537. rows = txn.fetchall()
  538. return rows[0][0]
  539. forgot = yield self.runInteraction("did_forget_membership_at", f)
  540. defer.returnValue(forgot == 1)
  541. @defer.inlineCallbacks
  542. def _background_add_membership_profile(self, progress, batch_size):
  543. target_min_stream_id = progress.get(
  544. "target_min_stream_id_inclusive", self._min_stream_order_on_start
  545. )
  546. max_stream_id = progress.get(
  547. "max_stream_id_exclusive", self._stream_order_on_start + 1
  548. )
  549. INSERT_CLUMP_SIZE = 1000
  550. def add_membership_profile_txn(txn):
  551. sql = ("""
  552. SELECT stream_ordering, event_id, events.room_id, content
  553. FROM events
  554. INNER JOIN room_memberships USING (event_id)
  555. WHERE ? <= stream_ordering AND stream_ordering < ?
  556. AND type = 'm.room.member'
  557. ORDER BY stream_ordering DESC
  558. LIMIT ?
  559. """)
  560. txn.execute(sql, (target_min_stream_id, max_stream_id, batch_size))
  561. rows = self.cursor_to_dict(txn)
  562. if not rows:
  563. return 0
  564. min_stream_id = rows[-1]["stream_ordering"]
  565. to_update = []
  566. for row in rows:
  567. event_id = row["event_id"]
  568. room_id = row["room_id"]
  569. try:
  570. content = json.loads(row["content"])
  571. except Exception:
  572. continue
  573. display_name = content.get("displayname", None)
  574. avatar_url = content.get("avatar_url", None)
  575. if display_name or avatar_url:
  576. to_update.append((
  577. display_name, avatar_url, event_id, room_id
  578. ))
  579. to_update_sql = ("""
  580. UPDATE room_memberships SET display_name = ?, avatar_url = ?
  581. WHERE event_id = ? AND room_id = ?
  582. """)
  583. for index in range(0, len(to_update), INSERT_CLUMP_SIZE):
  584. clump = to_update[index:index + INSERT_CLUMP_SIZE]
  585. txn.executemany(to_update_sql, clump)
  586. progress = {
  587. "target_min_stream_id_inclusive": target_min_stream_id,
  588. "max_stream_id_exclusive": min_stream_id,
  589. }
  590. self._background_update_progress_txn(
  591. txn, _MEMBERSHIP_PROFILE_UPDATE_NAME, progress
  592. )
  593. return len(rows)
  594. result = yield self.runInteraction(
  595. _MEMBERSHIP_PROFILE_UPDATE_NAME, add_membership_profile_txn
  596. )
  597. if not result:
  598. yield self._end_background_update(_MEMBERSHIP_PROFILE_UPDATE_NAME)
  599. defer.returnValue(result)
  600. class _JoinedHostsCache(object):
  601. """Cache for joined hosts in a room that is optimised to handle updates
  602. via state deltas.
  603. """
  604. def __init__(self, store, room_id):
  605. self.store = store
  606. self.room_id = room_id
  607. self.hosts_to_joined_users = {}
  608. self.state_group = object()
  609. self.linearizer = Linearizer("_JoinedHostsCache")
  610. self._len = 0
  611. @defer.inlineCallbacks
  612. def get_destinations(self, state_entry):
  613. """Get set of destinations for a state entry
  614. Args:
  615. state_entry(synapse.state._StateCacheEntry)
  616. """
  617. if state_entry.state_group == self.state_group:
  618. defer.returnValue(frozenset(self.hosts_to_joined_users))
  619. with (yield self.linearizer.queue(())):
  620. if state_entry.state_group == self.state_group:
  621. pass
  622. elif state_entry.prev_group == self.state_group:
  623. for (typ, state_key), event_id in state_entry.delta_ids.iteritems():
  624. if typ != EventTypes.Member:
  625. continue
  626. host = intern_string(get_domain_from_id(state_key))
  627. user_id = state_key
  628. known_joins = self.hosts_to_joined_users.setdefault(host, set())
  629. event = yield self.store.get_event(event_id)
  630. if event.membership == Membership.JOIN:
  631. known_joins.add(user_id)
  632. else:
  633. known_joins.discard(user_id)
  634. if not known_joins:
  635. self.hosts_to_joined_users.pop(host, None)
  636. else:
  637. joined_users = yield self.store.get_joined_users_from_state(
  638. self.room_id, state_entry,
  639. )
  640. self.hosts_to_joined_users = {}
  641. for user_id in joined_users:
  642. host = intern_string(get_domain_from_id(user_id))
  643. self.hosts_to_joined_users.setdefault(host, set()).add(user_id)
  644. if state_entry.state_group:
  645. self.state_group = state_entry.state_group
  646. else:
  647. self.state_group = object()
  648. self._len = sum(len(v) for v in self.hosts_to_joined_users.itervalues())
  649. defer.returnValue(frozenset(self.hosts_to_joined_users))
  650. def __len__(self):
  651. return self._len