You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

187 lines
6.7 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-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 typing import List, Tuple
  16. from twisted.internet import defer
  17. from synapse.storage._base import SQLBaseStore, make_in_list_sql_clause
  18. from synapse.storage.presence import UserPresenceState
  19. from synapse.util.caches.descriptors import cached, cachedList
  20. from synapse.util.iterutils import batch_iter
  21. class PresenceStore(SQLBaseStore):
  22. @defer.inlineCallbacks
  23. def update_presence(self, presence_states):
  24. stream_ordering_manager = self._presence_id_gen.get_next_mult(
  25. len(presence_states)
  26. )
  27. with stream_ordering_manager as stream_orderings:
  28. yield self.db_pool.runInteraction(
  29. "update_presence",
  30. self._update_presence_txn,
  31. stream_orderings,
  32. presence_states,
  33. )
  34. return stream_orderings[-1], self._presence_id_gen.get_current_token()
  35. def _update_presence_txn(self, txn, stream_orderings, presence_states):
  36. for stream_id, state in zip(stream_orderings, presence_states):
  37. txn.call_after(
  38. self.presence_stream_cache.entity_has_changed, state.user_id, stream_id
  39. )
  40. txn.call_after(self._get_presence_for_user.invalidate, (state.user_id,))
  41. # Actually insert new rows
  42. self.db_pool.simple_insert_many_txn(
  43. txn,
  44. table="presence_stream",
  45. values=[
  46. {
  47. "stream_id": stream_id,
  48. "user_id": state.user_id,
  49. "state": state.state,
  50. "last_active_ts": state.last_active_ts,
  51. "last_federation_update_ts": state.last_federation_update_ts,
  52. "last_user_sync_ts": state.last_user_sync_ts,
  53. "status_msg": state.status_msg,
  54. "currently_active": state.currently_active,
  55. }
  56. for stream_id, state in zip(stream_orderings, presence_states)
  57. ],
  58. )
  59. # Delete old rows to stop database from getting really big
  60. sql = "DELETE FROM presence_stream WHERE stream_id < ? AND "
  61. for states in batch_iter(presence_states, 50):
  62. clause, args = make_in_list_sql_clause(
  63. self.database_engine, "user_id", [s.user_id for s in states]
  64. )
  65. txn.execute(sql + clause, [stream_id] + list(args))
  66. async def get_all_presence_updates(
  67. self, instance_name: str, last_id: int, current_id: int, limit: int
  68. ) -> Tuple[List[Tuple[int, list]], int, bool]:
  69. """Get updates for presence replication stream.
  70. Args:
  71. instance_name: The writer we want to fetch updates from. Unused
  72. here since there is only ever one writer.
  73. last_id: The token to fetch updates from. Exclusive.
  74. current_id: The token to fetch updates up to. Inclusive.
  75. limit: The requested limit for the number of rows to return. The
  76. function may return more or fewer rows.
  77. Returns:
  78. A tuple consisting of: the updates, a token to use to fetch
  79. subsequent updates, and whether we returned fewer rows than exists
  80. between the requested tokens due to the limit.
  81. The token returned can be used in a subsequent call to this
  82. function to get further updatees.
  83. The updates are a list of 2-tuples of stream ID and the row data
  84. """
  85. if last_id == current_id:
  86. return [], current_id, False
  87. def get_all_presence_updates_txn(txn):
  88. sql = """
  89. SELECT stream_id, user_id, state, last_active_ts,
  90. last_federation_update_ts, last_user_sync_ts,
  91. status_msg,
  92. currently_active
  93. FROM presence_stream
  94. WHERE ? < stream_id AND stream_id <= ?
  95. ORDER BY stream_id ASC
  96. LIMIT ?
  97. """
  98. txn.execute(sql, (last_id, current_id, limit))
  99. updates = [(row[0], row[1:]) for row in txn]
  100. upper_bound = current_id
  101. limited = False
  102. if len(updates) >= limit:
  103. upper_bound = updates[-1][0]
  104. limited = True
  105. return updates, upper_bound, limited
  106. return await self.db_pool.runInteraction(
  107. "get_all_presence_updates", get_all_presence_updates_txn
  108. )
  109. @cached()
  110. def _get_presence_for_user(self, user_id):
  111. raise NotImplementedError()
  112. @cachedList(
  113. cached_method_name="_get_presence_for_user",
  114. list_name="user_ids",
  115. num_args=1,
  116. inlineCallbacks=True,
  117. )
  118. def get_presence_for_users(self, user_ids):
  119. rows = yield self.db_pool.simple_select_many_batch(
  120. table="presence_stream",
  121. column="user_id",
  122. iterable=user_ids,
  123. keyvalues={},
  124. retcols=(
  125. "user_id",
  126. "state",
  127. "last_active_ts",
  128. "last_federation_update_ts",
  129. "last_user_sync_ts",
  130. "status_msg",
  131. "currently_active",
  132. ),
  133. desc="get_presence_for_users",
  134. )
  135. for row in rows:
  136. row["currently_active"] = bool(row["currently_active"])
  137. return {row["user_id"]: UserPresenceState(**row) for row in rows}
  138. def get_current_presence_token(self):
  139. return self._presence_id_gen.get_current_token()
  140. def allow_presence_visible(self, observed_localpart, observer_userid):
  141. return self.db_pool.simple_insert(
  142. table="presence_allow_inbound",
  143. values={
  144. "observed_user_id": observed_localpart,
  145. "observer_user_id": observer_userid,
  146. },
  147. desc="allow_presence_visible",
  148. or_ignore=True,
  149. )
  150. def disallow_presence_visible(self, observed_localpart, observer_userid):
  151. return self.db_pool.simple_delete_one(
  152. table="presence_allow_inbound",
  153. keyvalues={
  154. "observed_user_id": observed_localpart,
  155. "observer_user_id": observer_userid,
  156. },
  157. desc="disallow_presence_visible",
  158. )