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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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