slidge 0.1.3__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 (74) 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 +6 -7
  6. slidge/command/base.py +1 -2
  7. slidge/command/register.py +32 -16
  8. slidge/command/user.py +85 -6
  9. slidge/contact/contact.py +165 -49
  10. slidge/contact/roster.py +122 -47
  11. slidge/core/config.py +14 -11
  12. slidge/core/gateway/base.py +148 -36
  13. slidge/core/gateway/caps.py +7 -5
  14. slidge/core/gateway/disco.py +2 -4
  15. slidge/core/gateway/mam.py +1 -4
  16. slidge/core/gateway/muc_admin.py +1 -1
  17. slidge/core/gateway/ping.py +2 -3
  18. slidge/core/gateway/presence.py +1 -1
  19. slidge/core/gateway/registration.py +32 -21
  20. slidge/core/gateway/search.py +3 -5
  21. slidge/core/gateway/session_dispatcher.py +120 -57
  22. slidge/core/gateway/vcard_temp.py +7 -5
  23. slidge/core/mixins/__init__.py +11 -1
  24. slidge/core/mixins/attachment.py +32 -14
  25. slidge/core/mixins/avatar.py +90 -25
  26. slidge/core/mixins/base.py +8 -2
  27. slidge/core/mixins/db.py +18 -0
  28. slidge/core/mixins/disco.py +0 -10
  29. slidge/core/mixins/message.py +18 -8
  30. slidge/core/mixins/message_maker.py +17 -9
  31. slidge/core/mixins/presence.py +17 -4
  32. slidge/core/pubsub.py +54 -220
  33. slidge/core/session.py +69 -34
  34. slidge/db/__init__.py +4 -0
  35. slidge/db/alembic/env.py +64 -0
  36. slidge/db/alembic/script.py.mako +26 -0
  37. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
  38. slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
  39. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
  40. slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
  41. slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
  42. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
  43. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +85 -0
  44. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
  45. slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +48 -0
  46. slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
  47. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
  48. slidge/db/avatar.py +235 -0
  49. slidge/db/meta.py +65 -0
  50. slidge/db/models.py +375 -0
  51. slidge/db/store.py +1078 -0
  52. slidge/group/archive.py +58 -14
  53. slidge/group/bookmarks.py +72 -57
  54. slidge/group/participant.py +87 -28
  55. slidge/group/room.py +369 -211
  56. slidge/main.py +201 -0
  57. slidge/migration.py +30 -0
  58. slidge/slixfix/__init__.py +35 -2
  59. slidge/slixfix/roster.py +11 -4
  60. slidge/slixfix/xep_0292/vcard4.py +3 -0
  61. slidge/util/archive_msg.py +2 -1
  62. slidge/util/db.py +1 -47
  63. slidge/util/test.py +71 -4
  64. slidge/util/types.py +29 -4
  65. slidge/util/util.py +22 -0
  66. {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/METADATA +4 -4
  67. slidge-0.2.0a1.dist-info/RECORD +114 -0
  68. slidge/core/cache.py +0 -183
  69. slidge/util/schema.sql +0 -126
  70. slidge/util/sql.py +0 -508
  71. slidge-0.1.3.dist-info/RECORD +0 -96
  72. {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/LICENSE +0 -0
  73. {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/WHEEL +0 -0
  74. {slidge-0.1.3.dist-info → slidge-0.2.0a1.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:
@@ -461,14 +478,17 @@ class SessionDispatcher:
461
478
 
462
479
  pto = p.get_to()
463
480
  if pto == self.xmpp.boundjid.bare:
464
- # NB: get_type() returns either a proper presence type or
465
- # a presence show if available. Weird, weird, weird slix.
481
+ session.log.debug("Received a presence from %s", p.get_from())
466
482
  if (ptype := p.get_type()) not in _USEFUL_PRESENCES:
467
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.
468
489
  resources = self.xmpp.roster[self.xmpp.boundjid.bare][
469
490
  p.get_from()
470
491
  ].resources
471
- session.log.debug("Received a presence from %s", p.get_from())
472
492
  await session.on_presence(
473
493
  p.get_from().resource,
474
494
  ptype, # type: ignore
@@ -478,8 +498,12 @@ class SessionDispatcher:
478
498
  )
479
499
  return
480
500
 
481
- muc = session.bookmarks._mucs_by_bare_jid.get(pto.bare)
482
- 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():
483
507
  return
484
508
 
485
509
  if pto.resource == muc.user_nick:
@@ -530,29 +554,36 @@ class SessionDispatcher:
530
554
  return
531
555
 
532
556
  stanza_id = msg["pubsub_event"]["items"]["item"]["displayed"]["stanza_id"]["id"]
533
- 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
+ )
534
560
 
535
561
  async def on_avatar_metadata_publish(self, m: Message):
536
- if not config.SYNC_AVATAR:
537
- return
538
-
539
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
540
566
  info = m["pubsub_event"]["items"]["item"]["avatar_metadata"]["info"]
541
567
 
542
568
  await self.on_avatar_metadata_info(session, info)
543
569
 
544
570
  async def on_avatar_metadata_info(self, session: BaseSession, info: Info):
545
- session.log.debug("Avatar metadata info: %s", info)
546
571
  hash_ = info["id"]
547
572
 
548
- if session.avatar_hash == hash_:
573
+ if session.user.avatar_hash == hash_:
574
+ session.log.debug("We already know this avatar hash")
549
575
  return
550
- 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()
551
582
 
552
583
  if hash_:
553
584
  try:
554
585
  iq = await self.xmpp.plugin["xep_0084"].retrieve_avatar(
555
- session.user.jid, hash_, ifrom=self.xmpp.boundjid.bare
586
+ session.user_jid, hash_, ifrom=self.xmpp.boundjid.bare
556
587
  )
557
588
  except IqError as e:
558
589
  session.log.warning("Could not fetch the user's avatar: %s", e)
@@ -593,7 +624,7 @@ class SessionDispatcher:
593
624
  "Slidge only implements moderation/retraction",
594
625
  )
595
626
 
596
- legacy_id = _xmpp_msg_id_to_legacy(session, xmpp_id)
627
+ legacy_id = self._xmpp_msg_id_to_legacy(session, xmpp_id)
597
628
  await session.on_moderate(muc, legacy_id, moderate["reason"] or None)
598
629
  iq.reply(clear=True).send()
599
630
 
@@ -601,14 +632,28 @@ class SessionDispatcher:
601
632
  session = await self.__get_session(iq)
602
633
  session.raise_if_not_logged()
603
634
 
604
- item = iq["mucadmin_query"]["item"]
605
- contact = await session.contacts.by_jid(JID(item["jid"]))
606
-
607
635
  muc = await session.bookmarks.by_jid(iq.get_to())
608
636
 
609
- await muc.on_set_affiliation(
610
- contact, item["affiliation"], item["reason"] or None, item["nick"] or None
611
- )
637
+ item = iq["mucadmin_query"]["item"]
638
+ if item["jid"]:
639
+ contact = await session.contacts.by_jid(JID(item["jid"]))
640
+ else:
641
+ part = await muc.get_participant(
642
+ item["nick"], fill_first=True, raise_if_not_found=True
643
+ )
644
+ assert part.contact is not None
645
+ contact = part.contact
646
+
647
+ if item["affiliation"]:
648
+ await muc.on_set_affiliation(
649
+ contact,
650
+ item["affiliation"],
651
+ item["reason"] or None,
652
+ item["nick"] or None,
653
+ )
654
+ elif item["role"] == "none":
655
+ await muc.on_kick(contact, item["reason"] or None)
656
+
612
657
  iq.reply(clear=True).send()
613
658
 
614
659
  async def on_groupchat_direct_invite(self, msg: Message):
@@ -718,25 +763,39 @@ class SessionDispatcher:
718
763
  )
719
764
  await muc.on_set_subject(msg["subject"])
720
765
 
766
+ async def on_ibr_remove(self, iq: Iq):
767
+ if iq.get_to() == self.xmpp.boundjid.bare:
768
+ return
721
769
 
722
- def _xmpp_msg_id_to_legacy(session: "BaseSession", xmpp_id: str):
723
- sent = session.sent.inverse.get(xmpp_id)
724
- if sent:
725
- return sent
770
+ session = await self.__get_session(iq)
771
+ session.raise_if_not_logged()
726
772
 
727
- multi = db.attachment_get_legacy_id_for_xmpp_id(xmpp_id)
728
- if multi:
729
- return multi
773
+ if iq["type"] == "set" and iq["register"]["remove"]:
774
+ muc = await session.bookmarks.by_jid(iq.get_to())
775
+ await session.on_leave_group(muc.legacy_id)
776
+ iq.reply().send()
777
+ return
730
778
 
731
- try:
732
- return session.xmpp_to_legacy_msg_id(xmpp_id)
733
- except XMPPError:
734
- raise
735
- except Exception as e:
736
- log.debug("Couldn't convert xmpp msg ID to legacy ID.", exc_info=e)
737
- raise XMPPError(
738
- "internal-server-error", "Couldn't convert xmpp msg ID to legacy ID."
739
- )
779
+ raise XMPPError("feature-not-implemented")
780
+
781
+ def _xmpp_msg_id_to_legacy(self, session: "BaseSession", xmpp_id: str):
782
+ sent = self.xmpp.store.sent.get_legacy_id(session.user_pk, xmpp_id)
783
+ if sent is not None:
784
+ return self.xmpp.LEGACY_MSG_ID_TYPE(sent)
785
+
786
+ multi = self.xmpp.store.multi.get_legacy_id(session.user_pk, xmpp_id)
787
+ if multi:
788
+ return self.xmpp.LEGACY_MSG_ID_TYPE(multi)
789
+
790
+ try:
791
+ return session.xmpp_to_legacy_msg_id(xmpp_id)
792
+ except XMPPError:
793
+ raise
794
+ except Exception as e:
795
+ log.debug("Couldn't convert xmpp msg ID to legacy ID.", exc_info=e)
796
+ raise XMPPError(
797
+ "internal-server-error", "Couldn't convert xmpp msg ID to legacy ID."
798
+ )
740
799
 
741
800
 
742
801
  def _ignore(session: "BaseSession", msg: Message):
@@ -755,14 +814,18 @@ async def _xmpp_to_legacy_thread(
755
814
  return
756
815
 
757
816
  if session.MESSAGE_IDS_ARE_THREAD_IDS:
758
- return session.threads.get(xmpp_thread)
817
+ return session.xmpp.store.sent.get_legacy_thread(session.user_pk, xmpp_thread)
759
818
 
760
819
  async with session.thread_creation_lock:
761
- legacy_thread = session.threads.get(xmpp_thread)
762
- if legacy_thread is None:
763
- legacy_thread = await recipient.create_thread(xmpp_thread)
764
- session.threads[xmpp_thread] = legacy_thread
765
- return legacy_thread
820
+ legacy_thread_str = session.xmpp.store.sent.get_legacy_thread(
821
+ session.user_pk, xmpp_thread
822
+ )
823
+ if legacy_thread_str is None:
824
+ legacy_thread = str(await recipient.create_thread(xmpp_thread))
825
+ session.xmpp.store.sent.set_thread(
826
+ session.user_pk, xmpp_thread, legacy_thread
827
+ )
828
+ return session.xmpp.LEGACY_MSG_ID_TYPE(legacy_thread)
766
829
 
767
830
 
768
831
  async def _get_entity(session: "BaseSession", m: Message) -> RecipientType:
@@ -770,7 +833,7 @@ async def _get_entity(session: "BaseSession", m: Message) -> RecipientType:
770
833
  if m.get_type() == "groupchat":
771
834
  muc = await session.bookmarks.by_jid(m.get_to())
772
835
  r = m.get_from().resource
773
- if r not in muc.user_resources:
836
+ if r not in muc.get_user_resources():
774
837
  session.create_task(muc.kick_resource(r))
775
838
  raise XMPPError("not-acceptable", "You are not connected to this chat")
776
839
  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,14 +54,14 @@ 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_
59
61
 
60
62
  async def __handle_get_vcard_temp(self, iq: Iq):
61
63
  session = self.xmpp.get_session_from_stanza(iq)
62
- entity = await session.get_contact_or_group_or_participant(iq.get_to())
64
+ entity = await session.get_contact_or_group_or_participant(iq.get_to(), False)
63
65
  if not entity:
64
66
  raise XMPPError("item-not-found")
65
67
 
@@ -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")
@@ -9,7 +9,7 @@ import warnings
9
9
  from datetime import datetime
10
10
  from mimetypes import guess_type
11
11
  from pathlib import Path
12
- from typing import IO, Collection, Optional, Sequence, Union
12
+ from typing import IO, AsyncIterator, Collection, Optional, Sequence, Union
13
13
  from urllib.parse import quote as urlquote
14
14
  from uuid import uuid4
15
15
  from xml.etree import ElementTree as ET
@@ -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
 
@@ -138,6 +141,7 @@ class AttachmentMixin(MessageMaker):
138
141
  async def __get_url(
139
142
  self,
140
143
  file_path: Optional[Path] = None,
144
+ async_data_stream: Optional[AsyncIterator[bytes]] = None,
141
145
  data_stream: Optional[IO[bytes]] = None,
142
146
  data: Optional[bytes] = None,
143
147
  file_url: Optional[str] = None,
@@ -146,13 +150,13 @@ class AttachmentMixin(MessageMaker):
146
150
  legacy_file_id: Optional[Union[str, int]] = None,
147
151
  ) -> tuple[bool, Optional[Path], str]:
148
152
  if legacy_file_id:
149
- cache = db.attachment_get_url(legacy_file_id)
153
+ cache = self.__store.get_url(str(legacy_file_id))
150
154
  if cache is not None:
151
155
  async with self.session.http.head(cache) as r:
152
156
  if r.status < 400:
153
157
  return False, None, cache
154
158
  else:
155
- db.attachment_remove(legacy_file_id)
159
+ self.__store.remove(str(legacy_file_id))
156
160
 
157
161
  if file_url and config.USE_ATTACHMENT_ORIGINAL_URLS:
158
162
  return False, None, file_url
@@ -173,14 +177,23 @@ class AttachmentMixin(MessageMaker):
173
177
  with file_path.open("wb") as f:
174
178
  f.write(await r.read())
175
179
 
176
- else:
177
- if data_stream is not None:
178
- data = data_stream.read()
180
+ elif data_stream is not None:
181
+ data = data_stream.read()
179
182
  if data is None:
180
183
  raise RuntimeError
181
184
 
182
185
  with file_path.open("wb") as f:
183
186
  f.write(data)
187
+ elif async_data_stream is not None:
188
+ # TODO: patch slixmpp to allow this as data source for
189
+ # upload_file() so we don't even have to write anything
190
+ # to disk.
191
+ with file_path.open("wb") as f:
192
+ async for chunk in async_data_stream:
193
+ f.write(chunk)
194
+ elif data is not None:
195
+ with file_path.open("wb") as f:
196
+ f.write(data)
184
197
 
185
198
  is_temp = not bool(config.NO_UPLOAD_PATH)
186
199
  else:
@@ -198,7 +211,7 @@ class AttachmentMixin(MessageMaker):
198
211
  local_path = file_path
199
212
  new_url = await self.__upload(file_path, file_name, content_type)
200
213
  if legacy_file_id:
201
- db.attachment_store_url(legacy_file_id, new_url)
214
+ self.__store.set_url(self.session.user_pk, str(legacy_file_id), new_url)
202
215
 
203
216
  return is_temp, local_path, new_url
204
217
 
@@ -211,7 +224,7 @@ class AttachmentMixin(MessageMaker):
211
224
  caption: Optional[str] = None,
212
225
  file_name: Optional[str] = None,
213
226
  ):
214
- cache = db.attachment_get_sims(uploaded_url)
227
+ cache = self.__store.get_sims(uploaded_url)
215
228
  if cache:
216
229
  msg.append(Sims(xml=ET.fromstring(cache)))
217
230
  return
@@ -238,7 +251,7 @@ class AttachmentMixin(MessageMaker):
238
251
  thumbnail["media-type"] = "image/blurhash"
239
252
  thumbnail["uri"] = "data:image/blurhash," + urlquote(h)
240
253
 
241
- db.attachment_store_sims(uploaded_url, str(sims))
254
+ self.__store.set_sims(uploaded_url, str(sims))
242
255
 
243
256
  msg.append(sims)
244
257
 
@@ -251,7 +264,7 @@ class AttachmentMixin(MessageMaker):
251
264
  caption: Optional[str] = None,
252
265
  file_name: Optional[str] = None,
253
266
  ):
254
- cache = db.attachment_get_sfs(uploaded_url)
267
+ cache = self.__store.get_sfs(uploaded_url)
255
268
  if cache:
256
269
  msg.append(StatelessFileSharing(xml=ET.fromstring(cache)))
257
270
  return
@@ -262,7 +275,7 @@ class AttachmentMixin(MessageMaker):
262
275
  sfs = self.xmpp["xep_0447"].get_sfs(path, [uploaded_url], content_type, caption)
263
276
  if file_name:
264
277
  sfs["file"]["name"] = file_name
265
- db.attachment_store_sfs(uploaded_url, str(sfs))
278
+ self.__store.set_sfs(uploaded_url, str(sfs))
266
279
 
267
280
  msg.append(sfs)
268
281
 
@@ -293,6 +306,7 @@ class AttachmentMixin(MessageMaker):
293
306
  file_path: Optional[Union[Path, str]] = None,
294
307
  legacy_msg_id: Optional[LegacyMessageType] = None,
295
308
  *,
309
+ async_data_stream: Optional[AsyncIterator[bytes]] = None,
296
310
  data_stream: Optional[IO[bytes]] = None,
297
311
  data: Optional[bytes] = None,
298
312
  file_url: Optional[str] = None,
@@ -309,6 +323,7 @@ class AttachmentMixin(MessageMaker):
309
323
  Send a single file from this :term:`XMPP Entity`.
310
324
 
311
325
  :param file_path: Path to the attachment
326
+ :param async_data_stream: Alternatively (and ideally) an AsyncIterator yielding bytes
312
327
  :param data_stream: Alternatively, a stream of bytes (such as a File object)
313
328
  :param data: Alternatively, a bytes object
314
329
  :param file_url: Alternatively, a URL
@@ -339,6 +354,7 @@ class AttachmentMixin(MessageMaker):
339
354
 
340
355
  is_temp, local_path, new_url = await self.__get_url(
341
356
  Path(file_path) if file_path else None,
357
+ async_data_stream,
342
358
  data_stream,
343
359
  data,
344
360
  file_url,
@@ -472,7 +488,9 @@ class AttachmentMixin(MessageMaker):
472
488
  ids.append(stanza_id["id"])
473
489
  else:
474
490
  ids.append(msg.get_id())
475
- db.attachment_store_legacy_to_multi_xmpp_msg_ids(legacy_msg_id, ids)
491
+ self.xmpp.store.multi.set_xmpp_ids(
492
+ self.session.user_pk, str(legacy_msg_id), ids
493
+ )
476
494
 
477
495
 
478
496
  def get_blurhash(path: Path, n=9) -> tuple[str, int, int]: