slidge 0.1.2__py3-none-any.whl → 0.2.0a0__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 (63) hide show
  1. slidge/__init__.py +3 -5
  2. slidge/__main__.py +2 -196
  3. slidge/__version__.py +5 -0
  4. slidge/command/adhoc.py +8 -1
  5. slidge/command/admin.py +5 -6
  6. slidge/command/base.py +1 -2
  7. slidge/command/register.py +32 -16
  8. slidge/command/user.py +85 -5
  9. slidge/contact/contact.py +93 -31
  10. slidge/contact/roster.py +54 -39
  11. slidge/core/config.py +13 -7
  12. slidge/core/gateway/base.py +139 -34
  13. slidge/core/gateway/disco.py +2 -4
  14. slidge/core/gateway/mam.py +1 -4
  15. slidge/core/gateway/ping.py +2 -3
  16. slidge/core/gateway/presence.py +1 -1
  17. slidge/core/gateway/registration.py +32 -21
  18. slidge/core/gateway/search.py +3 -5
  19. slidge/core/gateway/session_dispatcher.py +109 -51
  20. slidge/core/gateway/vcard_temp.py +6 -4
  21. slidge/core/mixins/__init__.py +11 -1
  22. slidge/core/mixins/attachment.py +15 -10
  23. slidge/core/mixins/avatar.py +66 -18
  24. slidge/core/mixins/base.py +8 -2
  25. slidge/core/mixins/message.py +11 -7
  26. slidge/core/mixins/message_maker.py +17 -9
  27. slidge/core/mixins/presence.py +14 -4
  28. slidge/core/pubsub.py +54 -212
  29. slidge/core/session.py +65 -33
  30. slidge/db/__init__.py +4 -0
  31. slidge/db/alembic/env.py +64 -0
  32. slidge/db/alembic/script.py.mako +26 -0
  33. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
  34. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
  35. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
  36. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +76 -0
  37. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
  38. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
  39. slidge/db/avatar.py +224 -0
  40. slidge/db/meta.py +65 -0
  41. slidge/db/models.py +365 -0
  42. slidge/db/store.py +976 -0
  43. slidge/group/archive.py +13 -14
  44. slidge/group/bookmarks.py +59 -56
  45. slidge/group/participant.py +81 -29
  46. slidge/group/room.py +242 -142
  47. slidge/main.py +201 -0
  48. slidge/migration.py +30 -0
  49. slidge/slixfix/__init__.py +35 -2
  50. slidge/slixfix/roster.py +11 -4
  51. slidge/slixfix/xep_0292/vcard4.py +1 -0
  52. slidge/util/db.py +1 -47
  53. slidge/util/test.py +21 -4
  54. slidge/util/types.py +24 -4
  55. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/METADATA +3 -1
  56. slidge-0.2.0a0.dist-info/RECORD +108 -0
  57. slidge/core/cache.py +0 -183
  58. slidge/util/schema.sql +0 -126
  59. slidge/util/sql.py +0 -508
  60. slidge-0.1.2.dist-info/RECORD +0 -96
  61. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/LICENSE +0 -0
  62. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/WHEEL +0 -0
  63. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/entry_points.txt +0 -0
@@ -9,7 +9,6 @@ from slixmpp.plugins.xep_0084.stanza import Info
9
9
 
10
10
  from ... import LegacyContact
11
11
  from ...group.room import LegacyMUC
12
- from ...util.sql import db
13
12
  from ...util.types import LinkPreview, Recipient, RecipientType
14
13
  from ...util.util import (
15
14
  dict_to_named_tuple,
@@ -62,6 +61,13 @@ class SessionDispatcher:
62
61
  _exceptions_to_xmpp_errors(self.on_muc_owner_set), # type: ignore
63
62
  )
64
63
  )
64
+ xmpp.register_handler(
65
+ CoroutineCallback(
66
+ "ibr_remove",
67
+ StanzaPath("/iq/register"),
68
+ _exceptions_to_xmpp_errors(self.on_ibr_remove), # type: ignore
69
+ )
70
+ )
65
71
 
66
72
  for event in (
67
73
  "legacy_message",
@@ -157,7 +163,7 @@ class SessionDispatcher:
157
163
  reply_fallback = None
158
164
  if msg.get_plugin("reply", check=True):
159
165
  try:
160
- reply_to_msg_xmpp_id = _xmpp_msg_id_to_legacy(
166
+ reply_to_msg_xmpp_id = self._xmpp_msg_id_to_legacy(
161
167
  session, msg["reply"]["id"]
162
168
  )
163
169
  except XMPPError:
@@ -170,7 +176,7 @@ class SessionDispatcher:
170
176
  else:
171
177
  reply_to_jid = JID(msg["reply"]["to"])
172
178
  if msg["type"] == "chat":
173
- if reply_to_jid.bare != session.user.jid.bare:
179
+ if reply_to_jid.bare != session.user_jid.bare:
174
180
  try:
175
181
  reply_to = await session.contacts.by_jid(reply_to_jid)
176
182
  except XMPPError:
@@ -242,13 +248,17 @@ class SessionDispatcher:
242
248
  if isinstance(e, LegacyMUC):
243
249
  await e.echo(msg, legacy_msg_id)
244
250
  if legacy_msg_id is not None:
245
- session.muc_sent_msg_ids[legacy_msg_id] = msg.get_id()
251
+ self.xmpp.store.sent.set_group_message(
252
+ session.user_pk, legacy_msg_id, msg.get_id()
253
+ )
246
254
  else:
247
255
  self.__ack(msg)
248
256
  if legacy_msg_id is not None:
249
- session.sent[legacy_msg_id] = msg.get_id()
257
+ self.xmpp.store.sent.set_message(
258
+ session.user_pk, legacy_msg_id, msg.get_id()
259
+ )
250
260
  if session.MESSAGE_IDS_ARE_THREAD_IDS and (t := msg["thread"]):
251
- session.threads[t] = legacy_msg_id
261
+ self.xmpp.store.sent.set_thread(session.user_pk, t, legacy_msg_id)
252
262
 
253
263
  async def on_groupchat_message(self, msg: Message):
254
264
  await self.on_legacy_message(msg)
@@ -257,9 +267,11 @@ class SessionDispatcher:
257
267
  session, entity, thread = await self.__get_session_entity_thread(msg)
258
268
  xmpp_id = msg["replace"]["id"]
259
269
  if isinstance(entity, LegacyMUC):
260
- legacy_id = session.muc_sent_msg_ids.inverse.get(xmpp_id)
270
+ legacy_id = self.xmpp.store.sent.get_group_legacy_id(
271
+ session.user_pk, xmpp_id
272
+ )
261
273
  else:
262
- legacy_id = _xmpp_msg_id_to_legacy(session, xmpp_id)
274
+ legacy_id = self._xmpp_msg_id_to_legacy(session, xmpp_id)
263
275
 
264
276
  if isinstance(entity, LegacyMUC):
265
277
  mentions = await entity.parse_mentions(msg["body"])
@@ -323,12 +335,16 @@ class SessionDispatcher:
323
335
 
324
336
  if isinstance(entity, LegacyMUC):
325
337
  if new_legacy_msg_id is not None:
326
- session.muc_sent_msg_ids[new_legacy_msg_id] = msg.get_id()
338
+ self.xmpp.store.sent.set_group_message(
339
+ session.user_pk, new_legacy_msg_id, msg.get_id()
340
+ )
327
341
  await entity.echo(msg, new_legacy_msg_id)
328
342
  else:
329
343
  self.__ack(msg)
330
344
  if new_legacy_msg_id is not None:
331
- session.sent[new_legacy_msg_id] = msg.get_id()
345
+ self.xmpp.store.sent.set_message(
346
+ session.user_pk, new_legacy_msg_id, msg.get_id()
347
+ )
332
348
 
333
349
  async def on_message_retract(self, msg: Message):
334
350
  session, entity, thread = await self.__get_session_entity_thread(msg)
@@ -338,7 +354,7 @@ class SessionDispatcher:
338
354
  "This legacy service does not support message retraction.",
339
355
  )
340
356
  xmpp_id: str = msg["retract"]["id"]
341
- legacy_id = _xmpp_msg_id_to_legacy(session, xmpp_id)
357
+ legacy_id = self._xmpp_msg_id_to_legacy(session, xmpp_id)
342
358
  if legacy_id:
343
359
  await session.on_retract(entity, legacy_id, thread=thread)
344
360
  if isinstance(entity, LegacyMUC):
@@ -362,7 +378,7 @@ class SessionDispatcher:
362
378
  to_mark = [displayed_msg_id]
363
379
  for xmpp_id in to_mark:
364
380
  await session.on_displayed(
365
- e, _xmpp_msg_id_to_legacy(session, xmpp_id), legacy_thread
381
+ e, self._xmpp_msg_id_to_legacy(session, xmpp_id), legacy_thread
366
382
  )
367
383
  if isinstance(e, LegacyMUC):
368
384
  await e.echo(msg, None)
@@ -397,7 +413,7 @@ class SessionDispatcher:
397
413
  if special_msg:
398
414
  legacy_id = react_to
399
415
  else:
400
- legacy_id = _xmpp_msg_id_to_legacy(session, react_to)
416
+ legacy_id = self._xmpp_msg_id_to_legacy(session, react_to)
401
417
 
402
418
  if not legacy_id:
403
419
  log.debug("Ignored reaction from user")
@@ -434,9 +450,10 @@ class SessionDispatcher:
434
450
  else:
435
451
  self.__ack(msg)
436
452
 
437
- multi = db.attachment_get_associated_xmpp_ids(react_to)
453
+ multi = self.xmpp.store.multi.get_xmpp_ids(session.user_pk, react_to)
438
454
  if not multi:
439
455
  return
456
+ multi = [m for m in multi if react_to != m]
440
457
 
441
458
  if isinstance(entity, LegacyMUC):
442
459
  for xmpp_id in multi:
@@ -448,18 +465,30 @@ class SessionDispatcher:
448
465
  entity.react(legacy_id, emojis, xmpp_id=xmpp_id, carbon=True)
449
466
 
450
467
  async def on_presence(self, p: Presence):
468
+ if p.get_plugin("muc_join", check=True):
469
+ # handled in on_groupchat_join
470
+ # without this early return, since we switch from and to in this
471
+ # presence stanza, on_groupchat_join ends up trying to instantiate
472
+ # a MUC with the user's JID, which in turn leads to slidge sending
473
+ # a (error) presence from=the user's JID, which terminates the
474
+ # XML stream.
475
+ return
476
+
451
477
  session = await self.__get_session(p)
452
478
 
453
479
  pto = p.get_to()
454
480
  if pto == self.xmpp.boundjid.bare:
455
- # NB: get_type() returns either a proper presence type or
456
- # a presence show if available. Weird, weird, weird slix.
481
+ session.log.debug("Received a presence from %s", p.get_from())
457
482
  if (ptype := p.get_type()) not in _USEFUL_PRESENCES:
458
483
  return
484
+ if not session.user.preferences.get("sync_presence", False):
485
+ session.log.debug("User does not want to sync their presence")
486
+ return
487
+ # NB: get_type() returns either a proper presence type or
488
+ # a presence show if available. Weird, weird, weird slix.
459
489
  resources = self.xmpp.roster[self.xmpp.boundjid.bare][
460
490
  p.get_from()
461
491
  ].resources
462
- session.log.debug("Received a presence from %s", p.get_from())
463
492
  await session.on_presence(
464
493
  p.get_from().resource,
465
494
  ptype, # type: ignore
@@ -469,8 +498,12 @@ class SessionDispatcher:
469
498
  )
470
499
  return
471
500
 
472
- muc = session.bookmarks._mucs_by_bare_jid.get(pto.bare)
473
- if muc is None or p.get_from().resource not in muc.user_resources:
501
+ muc = session.bookmarks.by_jid_only_if_exists(JID(pto.bare))
502
+
503
+ if muc is not None and p.get_type() == "unavailable":
504
+ return muc.on_presence_unavailable(p)
505
+
506
+ if muc is None or p.get_from().resource not in muc.get_user_resources():
474
507
  return
475
508
 
476
509
  if pto.resource == muc.user_nick:
@@ -521,29 +554,36 @@ class SessionDispatcher:
521
554
  return
522
555
 
523
556
  stanza_id = msg["pubsub_event"]["items"]["item"]["displayed"]["stanza_id"]["id"]
524
- await session.on_displayed(chat, _xmpp_msg_id_to_legacy(session, stanza_id))
557
+ await session.on_displayed(
558
+ chat, self._xmpp_msg_id_to_legacy(session, stanza_id)
559
+ )
525
560
 
526
561
  async def on_avatar_metadata_publish(self, m: Message):
527
- if not config.SYNC_AVATAR:
528
- return
529
-
530
562
  session = await self.__get_session(m, timeout=None)
563
+ if not session.user.preferences.get("sync_avatar", False):
564
+ session.log.debug("User does not want to sync their avatar")
565
+ return
531
566
  info = m["pubsub_event"]["items"]["item"]["avatar_metadata"]["info"]
532
567
 
533
568
  await self.on_avatar_metadata_info(session, info)
534
569
 
535
570
  async def on_avatar_metadata_info(self, session: BaseSession, info: Info):
536
- session.log.debug("Avatar metadata info: %s", info)
537
571
  hash_ = info["id"]
538
572
 
539
- if session.avatar_hash == hash_:
573
+ if session.user.avatar_hash == hash_:
574
+ session.log.debug("We already know this avatar hash")
540
575
  return
541
- session.avatar_hash = hash_
576
+ with self.xmpp.store.session() as orm_session:
577
+ user = self.xmpp.store.users.get(session.user_jid)
578
+ assert user is not None
579
+ user.avatar_hash = hash_
580
+ orm_session.add(user)
581
+ orm_session.commit()
542
582
 
543
583
  if hash_:
544
584
  try:
545
585
  iq = await self.xmpp.plugin["xep_0084"].retrieve_avatar(
546
- session.user.jid, hash_, ifrom=self.xmpp.boundjid.bare
586
+ session.user_jid, hash_, ifrom=self.xmpp.boundjid.bare
547
587
  )
548
588
  except IqError as e:
549
589
  session.log.warning("Could not fetch the user's avatar: %s", e)
@@ -584,7 +624,7 @@ class SessionDispatcher:
584
624
  "Slidge only implements moderation/retraction",
585
625
  )
586
626
 
587
- legacy_id = _xmpp_msg_id_to_legacy(session, xmpp_id)
627
+ legacy_id = self._xmpp_msg_id_to_legacy(session, xmpp_id)
588
628
  await session.on_moderate(muc, legacy_id, moderate["reason"] or None)
589
629
  iq.reply(clear=True).send()
590
630
 
@@ -709,25 +749,39 @@ class SessionDispatcher:
709
749
  )
710
750
  await muc.on_set_subject(msg["subject"])
711
751
 
752
+ async def on_ibr_remove(self, iq: Iq):
753
+ if iq.get_to() == self.xmpp.boundjid.bare:
754
+ return
755
+
756
+ session = await self.__get_session(iq)
757
+ session.raise_if_not_logged()
712
758
 
713
- def _xmpp_msg_id_to_legacy(session: "BaseSession", xmpp_id: str):
714
- sent = session.sent.inverse.get(xmpp_id)
715
- if sent:
716
- return sent
759
+ if iq["type"] == "set" and iq["register"]["remove"]:
760
+ muc = await session.bookmarks.by_jid(iq.get_to())
761
+ await session.on_leave_group(muc.legacy_id)
762
+ iq.reply().send()
763
+ return
717
764
 
718
- multi = db.attachment_get_legacy_id_for_xmpp_id(xmpp_id)
719
- if multi:
720
- return multi
765
+ raise XMPPError("feature-not-implemented")
721
766
 
722
- try:
723
- return session.xmpp_to_legacy_msg_id(xmpp_id)
724
- except XMPPError:
725
- raise
726
- except Exception as e:
727
- log.debug("Couldn't convert xmpp msg ID to legacy ID.", exc_info=e)
728
- raise XMPPError(
729
- "internal-server-error", "Couldn't convert xmpp msg ID to legacy ID."
730
- )
767
+ def _xmpp_msg_id_to_legacy(self, session: "BaseSession", xmpp_id: str):
768
+ sent = self.xmpp.store.sent.get_legacy_id(session.user_pk, xmpp_id)
769
+ if sent is not None:
770
+ return self.xmpp.LEGACY_MSG_ID_TYPE(sent)
771
+
772
+ multi = self.xmpp.store.multi.get_legacy_id(session.user_pk, xmpp_id)
773
+ if multi:
774
+ return self.xmpp.LEGACY_MSG_ID_TYPE(multi)
775
+
776
+ try:
777
+ return session.xmpp_to_legacy_msg_id(xmpp_id)
778
+ except XMPPError:
779
+ raise
780
+ except Exception as e:
781
+ log.debug("Couldn't convert xmpp msg ID to legacy ID.", exc_info=e)
782
+ raise XMPPError(
783
+ "internal-server-error", "Couldn't convert xmpp msg ID to legacy ID."
784
+ )
731
785
 
732
786
 
733
787
  def _ignore(session: "BaseSession", msg: Message):
@@ -746,14 +800,18 @@ async def _xmpp_to_legacy_thread(
746
800
  return
747
801
 
748
802
  if session.MESSAGE_IDS_ARE_THREAD_IDS:
749
- return session.threads.get(xmpp_thread)
803
+ return session.xmpp.store.sent.get_legacy_thread(session.user_pk, xmpp_thread)
750
804
 
751
805
  async with session.thread_creation_lock:
752
- legacy_thread = session.threads.get(xmpp_thread)
753
- if legacy_thread is None:
754
- legacy_thread = await recipient.create_thread(xmpp_thread)
755
- session.threads[xmpp_thread] = legacy_thread
756
- return legacy_thread
806
+ legacy_thread_str = session.xmpp.store.sent.get_legacy_thread(
807
+ session.user_pk, xmpp_thread
808
+ )
809
+ if legacy_thread_str is None:
810
+ legacy_thread = str(await recipient.create_thread(xmpp_thread))
811
+ session.xmpp.store.sent.set_thread(
812
+ session.user_pk, xmpp_thread, legacy_thread
813
+ )
814
+ return session.xmpp.LEGACY_MSG_ID_TYPE(legacy_thread)
757
815
 
758
816
 
759
817
  async def _get_entity(session: "BaseSession", m: Message) -> RecipientType:
@@ -761,7 +819,7 @@ async def _get_entity(session: "BaseSession", m: Message) -> RecipientType:
761
819
  if m.get_type() == "groupchat":
762
820
  muc = await session.bookmarks.by_jid(m.get_to())
763
821
  r = m.get_from().resource
764
- if r not in muc.user_resources:
822
+ if r not in muc.get_user_resources():
765
823
  session.create_task(muc.kick_resource(r))
766
824
  raise XMPPError("not-acceptable", "You are not connected to this chat")
767
825
  return muc
@@ -40,11 +40,13 @@ class VCardTemp:
40
40
  return await self.__handle_set_vcard_temp(iq)
41
41
 
42
42
  async def __fetch_user_avatar(self, session: BaseSession):
43
- hash_ = session.avatar_hash
43
+ hash_ = session.user.avatar_hash
44
44
  if not hash_:
45
- raise XMPPError("item-not-found", "This participant has no contact")
45
+ raise XMPPError(
46
+ "item-not-found", "The slidge user does not have any avatar set"
47
+ )
46
48
  meta_iq = await self.xmpp.plugin["xep_0060"].get_item(
47
- session.user.jid,
49
+ session.user_jid,
48
50
  MetaData.namespace,
49
51
  hash_,
50
52
  ifrom=self.xmpp.boundjid.bare,
@@ -52,7 +54,7 @@ class VCardTemp:
52
54
  info = meta_iq["pubsub"]["items"]["item"]["avatar_metadata"]["info"]
53
55
  type_ = info["type"]
54
56
  data_iq = await self.xmpp.plugin["xep_0084"].retrieve_avatar(
55
- session.user.jid, hash_, ifrom=self.xmpp.boundjid.bare
57
+ session.user_jid, hash_, ifrom=self.xmpp.boundjid.bare
56
58
  )
57
59
  bytes_ = data_iq["pubsub"]["items"]["item"]["avatar_data"]["value"]
58
60
  return bytes_, type_
@@ -2,6 +2,8 @@
2
2
  Mixins
3
3
  """
4
4
 
5
+ from typing import Optional
6
+
5
7
  from .avatar import AvatarMixin
6
8
  from .disco import ChatterDiscoMixin
7
9
  from .message import MessageCarbonMixin, MessageMixin
@@ -16,4 +18,12 @@ class FullCarbonMixin(ChatterDiscoMixin, MessageCarbonMixin, PresenceMixin):
16
18
  pass
17
19
 
18
20
 
19
- __all__ = ("AvatarMixin",)
21
+ class StoredAttributeMixin:
22
+ def serialize_extra_attributes(self) -> Optional[dict]:
23
+ return None
24
+
25
+ def deserialize_extra_attributes(self, data: dict) -> None:
26
+ pass
27
+
28
+
29
+ __all__ = ("AvatarMixin", "FullCarbonMixin", "StoredAttributeMixin")
@@ -22,7 +22,7 @@ from slixmpp.plugins.xep_0363 import FileUploadError
22
22
  from slixmpp.plugins.xep_0385.stanza import Sims
23
23
  from slixmpp.plugins.xep_0447.stanza import StatelessFileSharing
24
24
 
25
- from ...util.sql import db
25
+ from ...db.avatar import avatar_cache
26
26
  from ...util.types import (
27
27
  LegacyAttachment,
28
28
  LegacyMessageType,
@@ -31,11 +31,14 @@ from ...util.types import (
31
31
  )
32
32
  from ...util.util import fix_suffix
33
33
  from .. import config
34
- from ..cache import avatar_cache
35
34
  from .message_maker import MessageMaker
36
35
 
37
36
 
38
37
  class AttachmentMixin(MessageMaker):
38
+ def __init__(self, *a, **kw):
39
+ super().__init__(*a, **kw)
40
+ self.__store = self.xmpp.store.attachments
41
+
39
42
  def send_text(self, *_, **k) -> Optional[Message]:
40
43
  raise NotImplementedError
41
44
 
@@ -146,13 +149,13 @@ class AttachmentMixin(MessageMaker):
146
149
  legacy_file_id: Optional[Union[str, int]] = None,
147
150
  ) -> tuple[bool, Optional[Path], str]:
148
151
  if legacy_file_id:
149
- cache = db.attachment_get_url(legacy_file_id)
152
+ cache = self.__store.get_url(str(legacy_file_id))
150
153
  if cache is not None:
151
154
  async with self.session.http.head(cache) as r:
152
155
  if r.status < 400:
153
156
  return False, None, cache
154
157
  else:
155
- db.attachment_remove(legacy_file_id)
158
+ self.__store.remove(str(legacy_file_id))
156
159
 
157
160
  if file_url and config.USE_ATTACHMENT_ORIGINAL_URLS:
158
161
  return False, None, file_url
@@ -198,7 +201,7 @@ class AttachmentMixin(MessageMaker):
198
201
  local_path = file_path
199
202
  new_url = await self.__upload(file_path, file_name, content_type)
200
203
  if legacy_file_id:
201
- db.attachment_store_url(legacy_file_id, new_url)
204
+ self.__store.set_url(self.session.user_pk, str(legacy_file_id), new_url)
202
205
 
203
206
  return is_temp, local_path, new_url
204
207
 
@@ -211,7 +214,7 @@ class AttachmentMixin(MessageMaker):
211
214
  caption: Optional[str] = None,
212
215
  file_name: Optional[str] = None,
213
216
  ):
214
- cache = db.attachment_get_sims(uploaded_url)
217
+ cache = self.__store.get_sims(uploaded_url)
215
218
  if cache:
216
219
  msg.append(Sims(xml=ET.fromstring(cache)))
217
220
  return
@@ -238,7 +241,7 @@ class AttachmentMixin(MessageMaker):
238
241
  thumbnail["media-type"] = "image/blurhash"
239
242
  thumbnail["uri"] = "data:image/blurhash," + urlquote(h)
240
243
 
241
- db.attachment_store_sims(uploaded_url, str(sims))
244
+ self.__store.set_sims(uploaded_url, str(sims))
242
245
 
243
246
  msg.append(sims)
244
247
 
@@ -251,7 +254,7 @@ class AttachmentMixin(MessageMaker):
251
254
  caption: Optional[str] = None,
252
255
  file_name: Optional[str] = None,
253
256
  ):
254
- cache = db.attachment_get_sfs(uploaded_url)
257
+ cache = self.__store.get_sfs(uploaded_url)
255
258
  if cache:
256
259
  msg.append(StatelessFileSharing(xml=ET.fromstring(cache)))
257
260
  return
@@ -262,7 +265,7 @@ class AttachmentMixin(MessageMaker):
262
265
  sfs = self.xmpp["xep_0447"].get_sfs(path, [uploaded_url], content_type, caption)
263
266
  if file_name:
264
267
  sfs["file"]["name"] = file_name
265
- db.attachment_store_sfs(uploaded_url, str(sfs))
268
+ self.__store.set_sfs(uploaded_url, str(sfs))
266
269
 
267
270
  msg.append(sfs)
268
271
 
@@ -472,7 +475,9 @@ class AttachmentMixin(MessageMaker):
472
475
  ids.append(stanza_id["id"])
473
476
  else:
474
477
  ids.append(msg.get_id())
475
- db.attachment_store_legacy_to_multi_xmpp_msg_ids(legacy_msg_id, ids)
478
+ self.xmpp.store.multi.set_xmpp_ids(
479
+ self.session.user_pk, str(legacy_msg_id), ids
480
+ )
476
481
 
477
482
 
478
483
  def get_blurhash(path: Path, n=9) -> tuple[str, int, int]:
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Optional
5
5
 
6
6
  from slixmpp import JID
7
7
 
8
+ from ...db.avatar import CachedAvatar, avatar_cache
8
9
  from ...util.types import (
9
10
  URL,
10
11
  AnyBaseSession,
@@ -12,7 +13,6 @@ from ...util.types import (
12
13
  AvatarType,
13
14
  LegacyFileIdType,
14
15
  )
15
- from ..cache import avatar_cache
16
16
 
17
17
  if TYPE_CHECKING:
18
18
  from ..pubsub import PepAvatar
@@ -34,7 +34,9 @@ class AvatarMixin:
34
34
  def __init__(self) -> None:
35
35
  super().__init__()
36
36
  self._set_avatar_task: Optional[Task] = None
37
+ self.__broadcast_task: Optional[Task] = None
37
38
  self.__avatar_unique_id: Optional[AvatarIdType] = None
39
+ self._avatar_pk: Optional[int] = None
38
40
 
39
41
  @property
40
42
  def __avatar_jid(self):
@@ -86,13 +88,28 @@ class AvatarMixin:
86
88
 
87
89
  async def __set_avatar(self, a: Optional[AvatarType], uid: Optional[AvatarIdType]):
88
90
  self.__avatar_unique_id = uid
89
- await self.session.xmpp.pubsub.set_avatar(
90
- jid=self.__avatar_jid,
91
- avatar=a,
92
- unique_id=None if isinstance(uid, URL) else uid,
93
- broadcast_to=self.session.user.jid.bare,
94
- broadcast=self._avatar_pubsub_broadcast,
95
- )
91
+
92
+ if a is None:
93
+ cached_avatar = None
94
+ self._avatar_pk = None
95
+ else:
96
+ try:
97
+ cached_avatar = await avatar_cache.convert_or_get(
98
+ URL(a) if isinstance(a, URL) else a,
99
+ None if isinstance(uid, URL) else uid,
100
+ )
101
+ except Exception as e:
102
+ self.session.log.error("Failed to set avatar %s", a, exc_info=e)
103
+ self._avatar_pk = None
104
+ self.__avatar_unique_id = uid
105
+ return
106
+ self._avatar_pk = cached_avatar.pk
107
+
108
+ if self._avatar_pubsub_broadcast:
109
+ await self.session.xmpp.pubsub.broadcast_avatar(
110
+ self.__avatar_jid, self.session.user_jid, cached_avatar
111
+ )
112
+
96
113
  self._post_avatar_update()
97
114
 
98
115
  async def _no_change(self, a: Optional[AvatarType], uid: Optional[AvatarIdType]):
@@ -103,7 +120,7 @@ class AvatarMixin:
103
120
  if isinstance(uid, URL):
104
121
  if self.__avatar_unique_id != uid:
105
122
  return False
106
- return not await avatar_cache.url_has_changed(uid)
123
+ return not await avatar_cache.url_modified(uid)
107
124
  return self.__avatar_unique_id == uid
108
125
 
109
126
  async def set_avatar(
@@ -136,32 +153,63 @@ class AvatarMixin:
136
153
  if blocking:
137
154
  await awaitable
138
155
 
139
- def get_avatar(self) -> Optional["PepAvatar"]:
156
+ def get_cached_avatar(self) -> Optional["CachedAvatar"]:
140
157
  if not self.__avatar_unique_id:
141
158
  return None
142
- return self.session.xmpp.pubsub.get_avatar(self.__avatar_jid)
159
+ return avatar_cache.get(self.__avatar_unique_id)
160
+
161
+ def get_avatar(self) -> Optional["PepAvatar"]:
162
+ cached_avatar = self.get_cached_avatar()
163
+ if cached_avatar is None:
164
+ return None
165
+ from ..pubsub import PepAvatar
166
+
167
+ item = PepAvatar()
168
+ item.set_avatar_from_cache(cached_avatar)
169
+ return item
143
170
 
144
171
  def _post_avatar_update(self) -> None:
145
172
  return
146
173
 
174
+ def __get_cached_avatar_id(self):
175
+ i = self._get_cached_avatar_id()
176
+ if i is None:
177
+ return None
178
+ return self.session.xmpp.AVATAR_ID_TYPE(i)
179
+
180
+ def _get_cached_avatar_id(self) -> Optional[str]:
181
+ raise NotImplementedError
182
+
147
183
  async def avatar_wrap_update_info(self):
148
- cached_id = avatar_cache.get_cached_id_for(self.__avatar_jid)
184
+ cached_id = self.__get_cached_avatar_id()
149
185
  self.__avatar_unique_id = cached_id
150
186
  try:
151
187
  await self.update_info() # type:ignore
152
188
  except NotImplementedError:
153
189
  return
154
190
  new_id = self.avatar
155
- if isinstance(new_id, URL) and not await avatar_cache.url_has_changed(new_id):
191
+ if isinstance(new_id, URL) and not await avatar_cache.url_modified(new_id):
156
192
  return
157
193
  elif new_id != cached_id:
158
194
  # at this point it means that update_info set the avatar, and we don't
159
195
  # need to do anything else
160
196
  return
161
197
 
162
- await self.session.xmpp.pubsub.set_avatar_from_cache(
163
- self.__avatar_jid,
164
- new_id is None and cached_id is not None,
165
- self.session.user.jid.bare,
166
- self._avatar_pubsub_broadcast,
198
+ if self._avatar_pubsub_broadcast:
199
+ if new_id is None and cached_id is None:
200
+ return
201
+ cached_avatar = avatar_cache.get(cached_id)
202
+ self.__broadcast_task = self.session.xmpp.loop.create_task(
203
+ self.session.xmpp.pubsub.broadcast_avatar(
204
+ self.__avatar_jid, self.session.user_jid, cached_avatar
205
+ )
206
+ )
207
+
208
+ def _set_avatar_from_store(self, stored):
209
+ if stored.avatar_id is None:
210
+ return
211
+ self.__avatar_unique_id = (
212
+ stored.avatar.legacy_id
213
+ if stored.avatar.legacy_id is not None
214
+ else URL(stored.avatar.url)
167
215
  )
@@ -8,7 +8,6 @@ from ...util.types import MessageOrPresenceTypeVar
8
8
  if TYPE_CHECKING:
9
9
  from slidge.core.gateway import BaseGateway
10
10
  from slidge.core.session import BaseSession
11
- from slidge.util.db import GatewayUser
12
11
 
13
12
 
14
13
  class MetaBase(ABCMeta):
@@ -18,11 +17,18 @@ class MetaBase(ABCMeta):
18
17
  class Base:
19
18
  session: "BaseSession" = NotImplemented
20
19
  xmpp: "BaseGateway" = NotImplemented
21
- user: "GatewayUser" = NotImplemented
22
20
 
23
21
  jid: JID = NotImplemented
24
22
  name: str = NotImplemented
25
23
 
24
+ @property
25
+ def user_jid(self):
26
+ return self.session.user_jid
27
+
28
+ @property
29
+ def user_pk(self):
30
+ return self.session.user_pk
31
+
26
32
 
27
33
  class BaseSender(Base):
28
34
  def _send(