slidge 0.2.0a0__py3-none-any.whl → 0.2.0a1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. slidge/__version__.py +1 -1
  2. slidge/command/admin.py +1 -1
  3. slidge/command/user.py +0 -1
  4. slidge/contact/contact.py +86 -32
  5. slidge/contact/roster.py +79 -19
  6. slidge/core/config.py +1 -4
  7. slidge/core/gateway/base.py +9 -2
  8. slidge/core/gateway/caps.py +7 -5
  9. slidge/core/gateway/muc_admin.py +1 -1
  10. slidge/core/gateway/session_dispatcher.py +20 -6
  11. slidge/core/gateway/vcard_temp.py +1 -1
  12. slidge/core/mixins/attachment.py +17 -4
  13. slidge/core/mixins/avatar.py +26 -9
  14. slidge/core/mixins/db.py +18 -0
  15. slidge/core/mixins/disco.py +0 -10
  16. slidge/core/mixins/message.py +7 -1
  17. slidge/core/mixins/presence.py +6 -3
  18. slidge/core/pubsub.py +7 -15
  19. slidge/core/session.py +6 -3
  20. slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
  21. slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
  22. slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
  23. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +11 -2
  24. slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +48 -0
  25. slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
  26. slidge/db/avatar.py +20 -9
  27. slidge/db/models.py +16 -6
  28. slidge/db/store.py +217 -115
  29. slidge/group/archive.py +46 -1
  30. slidge/group/bookmarks.py +17 -5
  31. slidge/group/participant.py +10 -3
  32. slidge/group/room.py +183 -125
  33. slidge/main.py +3 -3
  34. slidge/slixfix/xep_0292/vcard4.py +2 -0
  35. slidge/util/archive_msg.py +2 -1
  36. slidge/util/test.py +54 -4
  37. slidge/util/types.py +5 -0
  38. slidge/util/util.py +22 -0
  39. {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/METADATA +2 -4
  40. {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/RECORD +43 -37
  41. {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/LICENSE +0 -0
  42. {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/WHEEL +0 -0
  43. {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/entry_points.txt +0 -0
@@ -28,7 +28,6 @@ class AvatarMixin:
28
28
 
29
29
  jid: JID = NotImplemented
30
30
  session: AnyBaseSession = NotImplemented
31
- _avatar_pubsub_broadcast: bool = NotImplemented
32
31
  _avatar_bare_jid: bool = NotImplemented
33
32
 
34
33
  def __init__(self) -> None:
@@ -86,7 +85,9 @@ class AvatarMixin:
86
85
  return None
87
86
  raise TypeError("Bad avatar", a)
88
87
 
89
- async def __set_avatar(self, a: Optional[AvatarType], uid: Optional[AvatarIdType]):
88
+ async def __set_avatar(
89
+ self, a: Optional[AvatarType], uid: Optional[AvatarIdType], delete: bool
90
+ ):
90
91
  self.__avatar_unique_id = uid
91
92
 
92
93
  if a is None:
@@ -105,13 +106,21 @@ class AvatarMixin:
105
106
  return
106
107
  self._avatar_pk = cached_avatar.pk
107
108
 
108
- if self._avatar_pubsub_broadcast:
109
+ if self.__should_pubsub_broadcast():
109
110
  await self.session.xmpp.pubsub.broadcast_avatar(
110
111
  self.__avatar_jid, self.session.user_jid, cached_avatar
111
112
  )
112
113
 
114
+ if delete and isinstance(a, Path):
115
+ a.unlink()
116
+
113
117
  self._post_avatar_update()
114
118
 
119
+ def __should_pubsub_broadcast(self):
120
+ return getattr(self, "is_friend", False) and getattr(
121
+ self, "added_to_roster", False
122
+ )
123
+
115
124
  async def _no_change(self, a: Optional[AvatarType], uid: Optional[AvatarIdType]):
116
125
  if a is None:
117
126
  return self.__avatar_unique_id is None
@@ -127,16 +136,20 @@ class AvatarMixin:
127
136
  self,
128
137
  a: Optional[AvatarType],
129
138
  avatar_unique_id: Optional[LegacyFileIdType] = None,
139
+ delete: bool = False,
130
140
  blocking=False,
131
141
  cancel=True,
132
142
  ) -> None:
133
143
  """
134
144
  Set an avatar for this entity
135
145
 
136
- :param a:
137
- :param avatar_unique_id:
138
- :param blocking:
139
- :param cancel:
146
+ :param a: The avatar, in one of the types slidge supports
147
+ :param avatar_unique_id: A globally unique ID for the avatar on the
148
+ legacy network
149
+ :param delete: If the avatar is provided as a Path, whether to delete
150
+ it once used or not.
151
+ :param blocking: Internal use by slidge for tests, do not use!
152
+ :param cancel: Internal use by slidge, do not use!
140
153
  """
141
154
  if avatar_unique_id is None and a is not None:
142
155
  avatar_unique_id = self.__get_uid(a)
@@ -145,7 +158,7 @@ class AvatarMixin:
145
158
  if cancel and self._set_avatar_task:
146
159
  self._set_avatar_task.cancel()
147
160
  awaitable = create_task(
148
- self.__set_avatar(a, avatar_unique_id),
161
+ self.__set_avatar(a, avatar_unique_id, delete),
149
162
  name=f"Set pubsub avatar of {self}",
150
163
  )
151
164
  if not self._set_avatar_task or self._set_avatar_task.done():
@@ -195,7 +208,7 @@ class AvatarMixin:
195
208
  # need to do anything else
196
209
  return
197
210
 
198
- if self._avatar_pubsub_broadcast:
211
+ if self.__should_pubsub_broadcast():
199
212
  if new_id is None and cached_id is None:
200
213
  return
201
214
  cached_avatar = avatar_cache.get(cached_id)
@@ -208,6 +221,10 @@ class AvatarMixin:
208
221
  def _set_avatar_from_store(self, stored):
209
222
  if stored.avatar_id is None:
210
223
  return
224
+ if stored.avatar is None:
225
+ # seems to happen after avatar cleanup for some reason?
226
+ self.__avatar_unique_id = None
227
+ return
211
228
  self.__avatar_unique_id = (
212
229
  stored.avatar.legacy_id
213
230
  if stored.avatar.legacy_id is not None
@@ -0,0 +1,18 @@
1
+ from contextlib import contextmanager
2
+
3
+
4
+ class UpdateInfoMixin:
5
+ """
6
+ This mixin just adds a context manager that prevents commiting to the DB
7
+ on every attribute change.
8
+ """
9
+
10
+ def __init__(self, *args, **kwargs):
11
+ super().__init__(*args, **kwargs)
12
+ self._updating_info = False
13
+
14
+ @contextmanager
15
+ def updating_info(self):
16
+ self._updating_info = True
17
+ yield
18
+ self._updating_info = False
@@ -13,10 +13,6 @@ class BaseDiscoMixin(Base):
13
13
  DISCO_NAME: str = NotImplemented
14
14
  DISCO_LANG = None
15
15
 
16
- def __init__(self):
17
- super().__init__()
18
- self.__caps_cache: Optional[str] = None
19
-
20
16
  def _get_disco_name(self):
21
17
  if self.DISCO_NAME is NotImplemented:
22
18
  return self.xmpp.COMPONENT_NAME
@@ -44,17 +40,11 @@ class BaseDiscoMixin(Base):
44
40
  return info
45
41
 
46
42
  async def get_caps_ver(self, jid: OptJid = None, node: Optional[str] = None):
47
- if self.__caps_cache:
48
- return self.__caps_cache
49
43
  info = await self.get_disco_info(jid, node)
50
44
  caps = self.xmpp.plugin["xep_0115"]
51
45
  ver = caps.generate_verstring(info, caps.hash)
52
- self.__caps_cache = ver
53
46
  return ver
54
47
 
55
- def reset_caps_cache(self):
56
- self.__caps_cache = None
57
-
58
48
 
59
49
  class ChatterDiscoMixin(BaseDiscoMixin):
60
50
  AVATAR = True
@@ -241,7 +241,13 @@ class ContentMessageMixin(AttachmentMixin):
241
241
  )
242
242
  if correction:
243
243
  msg["replace"]["id"] = self.__replace_id(legacy_msg_id)
244
- return self._send(msg, archive_only=archive_only, carbon=carbon, **send_kwargs)
244
+ return self._send(
245
+ msg,
246
+ archive_only=archive_only,
247
+ carbon=carbon,
248
+ legacy_msg_id=legacy_msg_id,
249
+ **send_kwargs,
250
+ )
245
251
 
246
252
  def correct(
247
253
  self,
@@ -19,27 +19,30 @@ _FRIEND_REQUEST_PRESENCES = {"subscribe", "unsubscribe", "subscribed", "unsubscr
19
19
 
20
20
  class PresenceMixin(BaseSender):
21
21
  _ONLY_SEND_PRESENCE_CHANGES = False
22
- contact_pk: Optional[int]
22
+ contact_pk: Optional[int] = None
23
23
 
24
24
  def __init__(self, *a, **k):
25
25
  super().__init__(*a, **k)
26
26
  # FIXME: this should not be an attribute of this mixin to allow garbage
27
27
  # collection of instances
28
28
  self.__update_last_seen_fallback_task: Optional[Task] = None
29
+ # this is only used when a presence is set during Contact.update_info(),
30
+ # when the contact does not have a DB primary key yet, and is written
31
+ # to DB at the end of update_info()
32
+ self.cached_presence: Optional[CachedPresence] = None
29
33
 
30
34
  async def __update_last_seen_fallback(self):
31
35
  await sleep(3600 * 7)
32
36
  self.send_last_presence(force=True, no_cache_online=False)
33
37
 
34
38
  def _get_last_presence(self) -> Optional[CachedPresence]:
35
- # TODO: use contact PK instead of JID
36
39
  if self.contact_pk is None:
37
40
  return None
38
41
  return self.xmpp.store.contacts.get_presence(self.contact_pk)
39
42
 
40
43
  def _store_last_presence(self, new: CachedPresence):
41
- # TODO: use contact PK instead of JID
42
44
  if self.contact_pk is None:
45
+ self.cached_presence = new
43
46
  return
44
47
  self.xmpp.store.contacts.set_presence(self.contact_pk, new)
45
48
 
slidge/core/pubsub.py CHANGED
@@ -20,8 +20,6 @@ from slixmpp.plugins.xep_0172 import UserNick
20
20
  from slixmpp.plugins.xep_0292.stanza import VCard4
21
21
  from slixmpp.types import JidStr, OptJidStr
22
22
 
23
- from ..contact.contact import LegacyContact
24
- from ..contact.roster import ContactIsUser
25
23
  from ..db.avatar import CachedAvatar, avatar_cache
26
24
  from ..db.store import ContactStore, SlidgeStore
27
25
  from .mixins.lock import NamedLockMixin
@@ -174,7 +172,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
174
172
  else:
175
173
  if pep_avatar.metadata is None:
176
174
  raise XMPPError("internal-server-error", "Avatar but no metadata?")
177
- await self.broadcast(
175
+ await self.__broadcast(
178
176
  data=pep_avatar.metadata,
179
177
  from_=p.get_to(),
180
178
  to=from_,
@@ -186,7 +184,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
186
184
  except XMPPError:
187
185
  pass
188
186
  else:
189
- await self.broadcast(data=pep_nick.nick, from_=p.get_to(), to=from_)
187
+ await self.__broadcast(data=pep_nick.nick, from_=p.get_to(), to=from_)
190
188
 
191
189
  if VCARD4_NAMESPACE + "+notify" in features:
192
190
  await self.broadcast_vcard_event(p.get_to(), to=from_)
@@ -201,7 +199,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
201
199
  # but movim expects it to be here, and I guess
202
200
 
203
201
  log.debug("Broadcast vcard4 event: %s", vcard)
204
- await self.broadcast(
202
+ await self.__broadcast(
205
203
  data=vcard,
206
204
  from_=JID(from_).bare,
207
205
  to=to,
@@ -296,7 +294,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
296
294
  result["pubsub"]["items"].append(item)
297
295
  result.send()
298
296
 
299
- async def broadcast(self, data, from_: JidStr, to: OptJidStr = None, **kwargs):
297
+ async def __broadcast(self, data, from_: JidStr, to: OptJidStr = None, **kwargs):
300
298
  from_ = JID(from_)
301
299
  if from_ != self.xmpp.boundjid.bare and to is not None:
302
300
  to = JID(to)
@@ -304,12 +302,6 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
304
302
  if session is None:
305
303
  return
306
304
  await session.ready
307
- try:
308
- entity = await session.get_contact_or_group_or_participant(from_)
309
- except ContactIsUser:
310
- return
311
- if isinstance(entity, LegacyContact) and not entity.is_friend:
312
- return
313
305
 
314
306
  item = EventItem()
315
307
  if data:
@@ -342,12 +334,12 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
342
334
  self, from_: JidStr, to: JidStr, cached_avatar: Optional[CachedAvatar]
343
335
  ) -> None:
344
336
  if cached_avatar is None:
345
- await self.broadcast(AvatarMetadata(), from_, to)
337
+ await self.__broadcast(AvatarMetadata(), from_, to)
346
338
  else:
347
339
  pep_avatar = PepAvatar()
348
340
  pep_avatar.set_avatar_from_cache(cached_avatar)
349
341
  assert pep_avatar.metadata is not None
350
- await self.broadcast(
342
+ await self.__broadcast(
351
343
  pep_avatar.metadata, from_, to, id=pep_avatar.metadata["info"]["id"]
352
344
  )
353
345
 
@@ -360,7 +352,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
360
352
  jid = JID(jid)
361
353
  nickname = PepNick(nick)
362
354
  log.debug("New nickname: %s", nickname.nick)
363
- self.xmpp.loop.create_task(self.broadcast(nickname.nick, jid, user_jid.bare))
355
+ self.xmpp.loop.create_task(self.__broadcast(nickname.nick, jid, user_jid.bare))
364
356
 
365
357
 
366
358
  log = logging.getLogger(__name__)
slidge/core/session.py CHANGED
@@ -481,14 +481,14 @@ class BaseSession(
481
481
  """
482
482
  await muc.on_set_affiliation(contact, "member", reason, None)
483
483
 
484
- async def on_leave_group(self, muc: LegacyMUC):
484
+ async def on_leave_group(self, muc_legacy_id: LegacyGroupIdType):
485
485
  """
486
486
  Triggered when the user leaves a group via the dedicated slidge command
487
487
  or the :xep:`0077` ``<remove />`` mechanism.
488
488
 
489
489
  This should be interpreted as definitely leaving the group.
490
490
 
491
- :param muc: The group to leave
491
+ :param muc_legacy_id: The legacy ID of the group to leave
492
492
  """
493
493
  raise NotImplementedError
494
494
 
@@ -728,7 +728,7 @@ class BaseSession(
728
728
  # No reason to override this
729
729
  self.xmpp.re_login(self)
730
730
 
731
- async def get_contact_or_group_or_participant(self, jid: JID):
731
+ async def get_contact_or_group_or_participant(self, jid: JID, create=True):
732
732
  if jid.bare in (contacts := self.contacts.known_contacts(only_friends=False)):
733
733
  return contacts[jid.bare]
734
734
  if (muc := self.bookmarks.by_jid_only_if_exists(JID(jid.bare))) is not None:
@@ -736,6 +736,9 @@ class BaseSession(
736
736
  else:
737
737
  muc = None
738
738
 
739
+ if not create:
740
+ return None
741
+
739
742
  try:
740
743
  return await self.contacts.by_jid(jid)
741
744
  except XMPPError:
@@ -0,0 +1,36 @@
1
+ """Store contacts caps verstring in DB
2
+
3
+ Revision ID: 2461390c0af2
4
+ Revises: 2b1f45ab7379
5
+ Create Date: 2024-07-20 08:00:11.675735
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "2461390c0af2"
16
+ down_revision: Union[str, None] = "2b1f45ab7379"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ with op.batch_alter_table("contact", schema=None) as batch_op:
24
+ batch_op.add_column(sa.Column("caps_ver_bare", sa.String(), nullable=True))
25
+ batch_op.add_column(sa.Column("caps_ver", sa.String(), nullable=True))
26
+
27
+ # ### end Alembic commands ###
28
+
29
+
30
+ def downgrade() -> None:
31
+ # ### commands auto generated by Alembic - please adjust! ###
32
+ with op.batch_alter_table("contact", schema=None) as batch_op:
33
+ batch_op.drop_column("caps_ver")
34
+ batch_op.drop_column("caps_ver_bare")
35
+
36
+ # ### end Alembic commands ###
@@ -0,0 +1,41 @@
1
+ """Store room subject setter by nickname
2
+
3
+ Revision ID: 2b1f45ab7379
4
+ Revises: c4a8ec35a0e8
5
+ Create Date: 2024-07-20 00:14:36.882689
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "2b1f45ab7379"
16
+ down_revision: Union[str, None] = "c4a8ec35a0e8"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ with op.batch_alter_table("room", schema=None) as batch_op:
24
+ batch_op.add_column(sa.Column("subject_setter", sa.String(), nullable=True))
25
+ batch_op.drop_constraint("subject_setter_id_foreign_key", type_="foreignkey")
26
+ batch_op.drop_column("subject_setter_id")
27
+ # ### end Alembic commands ###
28
+
29
+
30
+ def downgrade() -> None:
31
+ # ### commands auto generated by Alembic - please adjust! ###
32
+ with op.batch_alter_table("room", schema=None) as batch_op:
33
+ batch_op.add_column(sa.Column("subject_setter_id", sa.INTEGER(), nullable=True))
34
+ batch_op.create_foreign_key(
35
+ "subject_setter_id_foreign_key",
36
+ "participant",
37
+ ["subject_setter_id"],
38
+ ["id"],
39
+ )
40
+ batch_op.drop_column("subject_setter")
41
+ # ### end Alembic commands ###
@@ -0,0 +1,48 @@
1
+ """Add MUC.history_filled
2
+
3
+ Also drop caps_ver_bare column that should never have been added.
4
+
5
+ Revision ID: 82a4af84b679
6
+ Revises: 2461390c0af2
7
+ Create Date: 2024-07-22 07:01:05.352737
8
+
9
+ """
10
+
11
+ from typing import Sequence, Union
12
+
13
+ import sqlalchemy as sa
14
+ from alembic import op
15
+
16
+ # revision identifiers, used by Alembic.
17
+ revision: str = "82a4af84b679"
18
+ down_revision: Union[str, None] = "2461390c0af2"
19
+ branch_labels: Union[str, Sequence[str], None] = None
20
+ depends_on: Union[str, Sequence[str], None] = None
21
+
22
+
23
+ def upgrade() -> None:
24
+ # ### commands auto generated by Alembic - please adjust! ###
25
+ with op.batch_alter_table("contact", schema=None) as batch_op:
26
+ batch_op.drop_column("caps_ver_bare")
27
+
28
+ with op.batch_alter_table("room", schema=None) as batch_op:
29
+ batch_op.add_column(
30
+ sa.Column(
31
+ "history_filled",
32
+ sa.Boolean(),
33
+ nullable=False,
34
+ server_default=sa.False_(), # manually added
35
+ )
36
+ )
37
+ # ### end Alembic commands ###
38
+
39
+
40
+ def downgrade() -> None:
41
+ # ### commands auto generated by Alembic - please adjust! ###
42
+ with op.batch_alter_table("room", schema=None) as batch_op:
43
+ batch_op.drop_column("history_filled")
44
+
45
+ with op.batch_alter_table("contact", schema=None) as batch_op:
46
+ batch_op.add_column(sa.Column("caps_ver_bare", sa.VARCHAR(), nullable=True))
47
+
48
+ # ### end Alembic commands ###
@@ -9,6 +9,7 @@ Create Date: 2024-04-17 20:57:01.357041
9
9
  """
10
10
 
11
11
  import logging
12
+ from datetime import datetime
12
13
  from typing import Sequence, Union
13
14
 
14
15
  import sqlalchemy as sa
@@ -43,7 +44,11 @@ def upgrade() -> None:
43
44
  sa.UniqueConstraint("jid"),
44
45
  )
45
46
  # ### end Alembic commands ###
46
- migrate_from_shelf(accounts)
47
+ try:
48
+ migrate_from_shelf(accounts)
49
+ except Exception:
50
+ downgrade()
51
+ raise
47
52
 
48
53
 
49
54
  def downgrade() -> None:
@@ -67,7 +72,11 @@ def migrate_from_shelf(accounts: sa.Table) -> None:
67
72
  [
68
73
  {
69
74
  "jid": user.jid,
70
- "registration_date": user.registration_date,
75
+ "registration_date": (
76
+ user.registration_date
77
+ if user.registration_date is not None
78
+ else datetime.now()
79
+ ),
71
80
  "legacy_module_data": user.registration_form,
72
81
  "preferences": {},
73
82
  }
@@ -0,0 +1,48 @@
1
+ """Add source and legacy ID for archived messages
2
+
3
+ Revision ID: b64b1a793483
4
+ Revises: 82a4af84b679
5
+ Create Date: 2024-07-22 21:06:35.020569
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ from slidge.db.models import ArchivedMessage
15
+
16
+ # revision identifiers, used by Alembic.
17
+ revision: str = "b64b1a793483"
18
+ down_revision: Union[str, None] = "82a4af84b679"
19
+ branch_labels: Union[str, Sequence[str], None] = None
20
+ depends_on: Union[str, Sequence[str], None] = None
21
+
22
+
23
+ def upgrade() -> None:
24
+ # since we don't want source to be nullable, we drop all rows first.
25
+ # This is what you get by using alpha versions!
26
+ op.execute(sa.delete(ArchivedMessage))
27
+ # ### commands auto generated by Alembic - please adjust! ###
28
+
29
+ with op.batch_alter_table("mam", schema=None) as batch_op:
30
+ batch_op.add_column(
31
+ sa.Column(
32
+ "source",
33
+ sa.Enum("LIVE", "BACKFILL", name="archivedmessagesource"),
34
+ nullable=False,
35
+ )
36
+ )
37
+ batch_op.add_column(sa.Column("legacy_id", sa.String(), nullable=True))
38
+
39
+ # ### end Alembic commands ###
40
+
41
+
42
+ def downgrade() -> None:
43
+ # ### commands auto generated by Alembic - please adjust! ###
44
+ with op.batch_alter_table("mam", schema=None) as batch_op:
45
+ batch_op.drop_column("legacy_id")
46
+ batch_op.drop_column("source")
47
+
48
+ # ### end Alembic commands ###
@@ -0,0 +1,34 @@
1
+ """Per-room user nick
2
+
3
+ Revision ID: c4a8ec35a0e8
4
+ Revises: 09f27f098baa
5
+ Create Date: 2024-07-12 06:27:47.397925
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "c4a8ec35a0e8"
16
+ down_revision: Union[str, None] = "09f27f098baa"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ with op.batch_alter_table("room", schema=None) as batch_op:
24
+ batch_op.add_column(sa.Column("user_nick", sa.String(), nullable=True))
25
+
26
+ # ### end Alembic commands ###
27
+
28
+
29
+ def downgrade() -> None:
30
+ # ### commands auto generated by Alembic - please adjust! ###
31
+ with op.batch_alter_table("room", schema=None) as batch_op:
32
+ batch_op.drop_column("user_nick")
33
+
34
+ # ### end Alembic commands ###
slidge/db/avatar.py CHANGED
@@ -70,6 +70,17 @@ class AvatarCache:
70
70
  def set_dir(self, path: Path):
71
71
  self.dir = path
72
72
  self.dir.mkdir(exist_ok=True)
73
+ with self.store.session():
74
+ for stored in self.store.get_all():
75
+ avatar = CachedAvatar.from_store(stored, root_dir=path)
76
+ if avatar.path.exists():
77
+ continue
78
+ log.warning(
79
+ "Removing avatar %s from store because %s does not exist",
80
+ avatar.hash,
81
+ avatar.path,
82
+ )
83
+ self.store.delete_by_pk(stored.id)
73
84
 
74
85
  def close(self):
75
86
  self._thread_pool.shutdown(cancel_futures=True)
@@ -142,15 +153,15 @@ class AvatarCache:
142
153
 
143
154
  if isinstance(avatar, (URL, str)):
144
155
  if unique_id is None:
145
- stored = self.store.get_by_url(avatar)
146
- try:
147
- img, response_headers = await self.__download(
148
- avatar, self.__get_http_headers(stored)
149
- )
150
- except NotModified:
151
- assert stored is not None
152
- return CachedAvatar.from_store(stored, self.dir)
153
-
156
+ with self.store.session():
157
+ stored = self.store.get_by_url(avatar)
158
+ try:
159
+ img, response_headers = await self.__download(
160
+ avatar, self.__get_http_headers(stored)
161
+ )
162
+ except NotModified:
163
+ assert stored is not None
164
+ return CachedAvatar.from_store(stored, self.dir)
154
165
  else:
155
166
  img, _ = await self.__download(avatar, {})
156
167
  response_headers = None
slidge/db/models.py CHANGED
@@ -24,6 +24,16 @@ class XmppToLegacyEnum(IntEnum):
24
24
  THREAD = 3
25
25
 
26
26
 
27
+ class ArchivedMessageSource(IntEnum):
28
+ """
29
+ Whether an archived message comes from ``LegacyMUC.backfill()`` or was received
30
+ as a "live" message.
31
+ """
32
+
33
+ LIVE = 1
34
+ BACKFILL = 2
35
+
36
+
27
37
  class GatewayUser(Base):
28
38
  """
29
39
  A user, registered to the gateway component.
@@ -145,6 +155,7 @@ class Contact(Base):
145
155
  ptype: Mapped[Optional[str]] = mapped_column(nullable=True)
146
156
  pstatus: Mapped[Optional[str]] = mapped_column(nullable=True)
147
157
  pshow: Mapped[Optional[str]] = mapped_column(nullable=True)
158
+ caps_ver: Mapped[Optional[str]] = mapped_column(nullable=True)
148
159
 
149
160
  is_friend: Mapped[bool] = mapped_column(default=False)
150
161
  added_to_roster: Mapped[bool] = mapped_column(default=False)
@@ -195,20 +206,17 @@ class Room(Base):
195
206
  description: Mapped[Optional[str]] = mapped_column(nullable=True)
196
207
  subject: Mapped[Optional[str]] = mapped_column(nullable=True)
197
208
  subject_date: Mapped[Optional[datetime]] = mapped_column(nullable=True)
198
- subject_setter_id: Mapped[Optional[int]] = mapped_column(
199
- ForeignKey("participant.id"), nullable=True
200
- )
201
- subject_setter: Mapped["Participant"] = relationship(
202
- foreign_keys="Room.subject_setter_id"
203
- )
209
+ subject_setter: Mapped[Optional[str]] = mapped_column(nullable=True)
204
210
 
205
211
  n_participants: Mapped[Optional[int]] = mapped_column(default=None)
206
212
 
207
213
  muc_type: Mapped[Optional[MucType]] = mapped_column(default=MucType.GROUP)
208
214
 
215
+ user_nick: Mapped[Optional[str]] = mapped_column()
209
216
  user_resources: Mapped[Optional[str]] = mapped_column(nullable=True)
210
217
 
211
218
  participants_filled: Mapped[bool] = mapped_column(default=False)
219
+ history_filled: Mapped[bool] = mapped_column(default=False)
212
220
 
213
221
  extra_attributes: Mapped[Optional[JSONSerializable]] = mapped_column(default=None)
214
222
  updated: Mapped[bool] = mapped_column(default=False)
@@ -232,6 +240,8 @@ class ArchivedMessage(Base):
232
240
  stanza_id: Mapped[str] = mapped_column(nullable=False)
233
241
  timestamp: Mapped[datetime] = mapped_column(nullable=False)
234
242
  author_jid: Mapped[JID] = mapped_column(nullable=False)
243
+ source: Mapped[ArchivedMessageSource] = mapped_column(nullable=False)
244
+ legacy_id: Mapped[Optional[str]] = mapped_column(nullable=True)
235
245
 
236
246
  stanza: Mapped[str] = mapped_column(nullable=False)
237
247