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
slidge/db/store.py CHANGED
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import logging
5
- import warnings
6
5
  from contextlib import contextmanager
7
6
  from datetime import datetime, timedelta, timezone
8
7
  from typing import TYPE_CHECKING, Collection, Iterator, Optional, Type
@@ -11,6 +10,7 @@ from slixmpp import JID, Iq, Message, Presence
11
10
  from slixmpp.exceptions import XMPPError
12
11
  from sqlalchemy import Engine, delete, select, update
13
12
  from sqlalchemy.orm import Session, attributes
13
+ from sqlalchemy.sql.functions import count
14
14
 
15
15
  from ..util.archive_msg import HistoryMessage
16
16
  from ..util.types import URL, CachedPresence
@@ -19,6 +19,7 @@ from ..util.types import MamMetadata, MucAffiliation, MucRole
19
19
  from .meta import Base
20
20
  from .models import (
21
21
  ArchivedMessage,
22
+ ArchivedMessageSource,
22
23
  Attachment,
23
24
  Avatar,
24
25
  Contact,
@@ -63,9 +64,18 @@ class UpdatedMixin(EngineMixin):
63
64
  def __init__(self, *a, **kw):
64
65
  super().__init__(*a, **kw)
65
66
  with self.session() as session:
67
+ session.execute(
68
+ delete(self.model).where(~self.model.updated) # type:ignore
69
+ )
66
70
  session.execute(update(self.model).values(updated=False)) # type:ignore
67
71
  session.commit()
68
72
 
73
+ def get_by_pk(self, pk: int) -> Optional[Base]:
74
+ with self.session() as session:
75
+ return session.execute(
76
+ select(self.model).where(self.model.id == pk) # type:ignore
77
+ ).scalar()
78
+
69
79
 
70
80
  class SlidgeStore(EngineMixin):
71
81
  def __init__(self, engine: Engine):
@@ -134,34 +144,18 @@ class AvatarStore(EngineMixin):
134
144
  select(Avatar).where(Avatar.legacy_id == legacy_id)
135
145
  ).scalar()
136
146
 
137
- def store_jid(self, sha: str, jid: JID) -> None:
147
+ def get_by_pk(self, pk: int) -> Optional[Avatar]:
138
148
  with self.session() as session:
139
- pk = session.execute(select(Avatar.id).where(Avatar.hash == sha)).scalar()
140
- if pk is None:
141
- warnings.warn("avatar not found")
142
- return
143
- session.execute(
144
- update(Contact).where(Contact.jid == jid.bare).values(avatar_id=pk)
145
- )
146
- session.execute(
147
- update(Room).where(Room.jid == jid.bare).values(avatar_id=pk)
148
- )
149
- session.commit()
149
+ return session.execute(select(Avatar).where(Avatar.id == pk)).scalar()
150
150
 
151
- def get_by_jid(self, jid: JID) -> Optional[Avatar]:
151
+ def delete_by_pk(self, pk: int):
152
152
  with self.session() as session:
153
- avatar = session.execute(
154
- select(Avatar).where(Avatar.contacts.any(Contact.jid == jid))
155
- ).scalar()
156
- if avatar is not None:
157
- return avatar
158
- return session.execute(
159
- select(Avatar).where(Avatar.rooms.any(Room.jid == jid))
160
- ).scalar()
153
+ session.execute(delete(Avatar).where(Avatar.id == pk))
154
+ session.commit()
161
155
 
162
- def get_by_pk(self, pk: int) -> Optional[Avatar]:
156
+ def get_all(self) -> Iterator[Avatar]:
163
157
  with self.session() as session:
164
- return session.execute(select(Avatar).where(Avatar.id == pk)).scalar()
158
+ yield from session.execute(select(Avatar)).scalars()
165
159
 
166
160
 
167
161
  class SentStore(EngineMixin):
@@ -273,22 +267,6 @@ class ContactStore(UpdatedMixin):
273
267
  session.execute(update(Contact).values(cached_presence=False))
274
268
  session.commit()
275
269
 
276
- def add(self, user_pk: int, legacy_id: str, contact_jid: JID) -> int:
277
- with self.session() as session:
278
- existing = session.execute(
279
- select(Contact)
280
- .where(Contact.legacy_id == legacy_id)
281
- .where(Contact.jid == contact_jid.bare)
282
- ).scalar()
283
- if existing is not None:
284
- return existing.id
285
- contact = Contact(
286
- jid=contact_jid.bare, legacy_id=legacy_id, user_account_id=user_pk
287
- )
288
- session.add(contact)
289
- session.commit()
290
- return contact.id
291
-
292
270
  def get_all(self, user_pk: int) -> Iterator[Contact]:
293
271
  with self.session() as session:
294
272
  yield from session.execute(
@@ -375,20 +353,36 @@ class ContactStore(UpdatedMixin):
375
353
  return None
376
354
  return contact.avatar.legacy_id
377
355
 
378
- def update(self, contact: "LegacyContact"):
356
+ def update(self, contact: "LegacyContact", commit=True) -> int:
379
357
  with self.session() as session:
380
- session.execute(
381
- update(Contact)
382
- .where(Contact.id == contact.contact_pk)
383
- .values(
384
- nick=contact.name,
385
- is_friend=contact.is_friend,
386
- added_to_roster=contact.added_to_roster,
387
- updated=True,
388
- extra_attributes=contact.serialize_extra_attributes(),
358
+ if contact.contact_pk is None:
359
+ if contact.cached_presence is not None:
360
+ presence_kwargs = contact.cached_presence._asdict()
361
+ presence_kwargs["cached_presence"] = True
362
+ else:
363
+ presence_kwargs = {}
364
+ row = Contact(
365
+ jid=contact.jid.bare,
366
+ legacy_id=str(contact.legacy_id),
367
+ user_account_id=contact.user_pk,
368
+ **presence_kwargs,
389
369
  )
390
- )
391
- session.commit()
370
+ else:
371
+ row = (
372
+ session.query(Contact)
373
+ .filter(Contact.id == contact.contact_pk)
374
+ .one()
375
+ )
376
+ row.nick = contact.name
377
+ row.is_friend = contact.is_friend
378
+ row.added_to_roster = contact.added_to_roster
379
+ row.updated = True
380
+ row.extra_attributes = contact.serialize_extra_attributes()
381
+ row.caps_ver = contact._caps_ver
382
+ session.add(row)
383
+ if commit:
384
+ session.commit()
385
+ return row.id
392
386
 
393
387
  def add_to_sent(self, contact_pk: int, msg_id: str) -> None:
394
388
  with self.session() as session:
@@ -422,19 +416,52 @@ class ContactStore(UpdatedMixin):
422
416
  )
423
417
  session.commit()
424
418
 
419
+ def set_added_to_roster(self, contact_pk: int, value: bool) -> None:
420
+ with self.session() as session:
421
+ session.execute(
422
+ update(Contact)
423
+ .where(Contact.id == contact_pk)
424
+ .values(added_to_roster=value)
425
+ )
426
+ session.commit()
427
+
428
+ def delete(self, contact_pk: int) -> None:
429
+ with self.session() as session:
430
+ session.execute(delete(Contact).where(Contact.id == contact_pk))
431
+ session.commit()
432
+
425
433
 
426
434
  class MAMStore(EngineMixin):
435
+ def __init__(self, *a, **kw):
436
+ super().__init__(*a, **kw)
437
+ with self.session() as session:
438
+ session.execute(
439
+ update(ArchivedMessage).values(source=ArchivedMessageSource.BACKFILL)
440
+ )
441
+ session.commit()
442
+
427
443
  def nuke_older_than(self, days: int) -> None:
428
444
  with self.session() as session:
429
445
  session.execute(
430
446
  delete(ArchivedMessage).where(
431
- ArchivedMessage.timestamp > datetime.now() - timedelta(days=days)
447
+ ArchivedMessage.timestamp < datetime.now() - timedelta(days=days)
432
448
  )
433
449
  )
434
450
  session.commit()
435
451
 
436
- def add_message(self, room_pk: int, message: HistoryMessage) -> None:
452
+ def add_message(
453
+ self,
454
+ room_pk: int,
455
+ message: HistoryMessage,
456
+ archive_only: bool,
457
+ legacy_msg_id: str | None,
458
+ ) -> None:
437
459
  with self.session() as session:
460
+ source = (
461
+ ArchivedMessageSource.BACKFILL
462
+ if archive_only
463
+ else ArchivedMessageSource.LIVE
464
+ )
438
465
  existing = session.execute(
439
466
  select(ArchivedMessage)
440
467
  .where(ArchivedMessage.room_id == room_pk)
@@ -445,6 +472,8 @@ class MAMStore(EngineMixin):
445
472
  existing.timestamp = message.when
446
473
  existing.stanza = str(message.stanza)
447
474
  existing.author_jid = message.stanza.get_from()
475
+ existing.source = source
476
+ existing.legacy_id = legacy_msg_id
448
477
  session.add(existing)
449
478
  session.commit()
450
479
  return
@@ -454,6 +483,8 @@ class MAMStore(EngineMixin):
454
483
  stanza=str(message.stanza),
455
484
  author_jid=message.stanza.get_from(),
456
485
  room_id=room_pk,
486
+ source=source,
487
+ legacy_id=legacy_msg_id,
457
488
  )
458
489
  session.add(mam_msg)
459
490
  session.commit()
@@ -526,25 +557,83 @@ class MAMStore(EngineMixin):
526
557
  stanza=str(h.stanza), when=h.timestamp.replace(tzinfo=timezone.utc)
527
558
  )
528
559
 
529
- def get_first_and_last(self, room_pk: int) -> list[MamMetadata]:
530
- r = []
560
+ def get_first(self, room_pk: int, with_legacy_id=False) -> ArchivedMessage | None:
531
561
  with self.session() as session:
532
- first = session.execute(
533
- select(ArchivedMessage.stanza_id, ArchivedMessage.timestamp)
562
+ q = (
563
+ select(ArchivedMessage)
534
564
  .where(ArchivedMessage.room_id == room_pk)
535
565
  .order_by(ArchivedMessage.timestamp.asc())
536
- ).first()
566
+ )
567
+ if with_legacy_id:
568
+ q = q.filter(ArchivedMessage.legacy_id.isnot(None))
569
+ return session.execute(q).scalar()
570
+
571
+ def get_last(
572
+ self, room_pk: int, source: ArchivedMessageSource | None = None
573
+ ) -> ArchivedMessage | None:
574
+ with self.session() as session:
575
+ q = select(ArchivedMessage).where(ArchivedMessage.room_id == room_pk)
576
+
577
+ if source is not None:
578
+ q = q.where(ArchivedMessage.source == source)
579
+
580
+ return session.execute(
581
+ q.order_by(ArchivedMessage.timestamp.desc())
582
+ ).scalar()
583
+
584
+ def get_first_and_last(self, room_pk: int) -> list[MamMetadata]:
585
+ r = []
586
+ with self.session():
587
+ first = self.get_first(room_pk)
537
588
  if first is not None:
538
- r.append(MamMetadata(*first))
539
- last = session.execute(
540
- select(ArchivedMessage.stanza_id, ArchivedMessage.timestamp)
541
- .where(ArchivedMessage.room_id == room_pk)
542
- .order_by(ArchivedMessage.timestamp.desc())
543
- ).first()
589
+ r.append(MamMetadata(first.stanza_id, first.timestamp))
590
+ last = self.get_last(room_pk)
544
591
  if last is not None:
545
- r.append(MamMetadata(*last))
592
+ r.append(MamMetadata(last.stanza_id, last.timestamp))
546
593
  return r
547
594
 
595
+ def get_most_recent_with_legacy_id(
596
+ self, room_pk: int, source: ArchivedMessageSource | None = None
597
+ ) -> ArchivedMessage | None:
598
+ with self.session() as session:
599
+ q = (
600
+ select(ArchivedMessage)
601
+ .where(ArchivedMessage.room_id == room_pk)
602
+ .where(ArchivedMessage.legacy_id.isnot(None))
603
+ )
604
+ if source is not None:
605
+ q = q.where(ArchivedMessage.source == source)
606
+ return session.execute(
607
+ q.order_by(ArchivedMessage.timestamp.desc())
608
+ ).scalar()
609
+
610
+ def get_least_recent_with_legacy_id_after(
611
+ self, room_pk: int, after_id: str, source=ArchivedMessageSource.LIVE
612
+ ) -> ArchivedMessage | None:
613
+ with self.session() as session:
614
+ after_timestamp = (
615
+ session.query(ArchivedMessage.timestamp)
616
+ .filter(ArchivedMessage.legacy_id == after_id)
617
+ .scalar()
618
+ )
619
+ q = (
620
+ select(ArchivedMessage)
621
+ .where(ArchivedMessage.room_id == room_pk)
622
+ .where(ArchivedMessage.legacy_id.isnot(None))
623
+ .where(ArchivedMessage.source == source)
624
+ .where(ArchivedMessage.timestamp > after_timestamp)
625
+ )
626
+ return session.execute(q.order_by(ArchivedMessage.timestamp.asc())).scalar()
627
+
628
+ def get_by_legacy_id(self, room_pk: int, legacy_id: str) -> ArchivedMessage | None:
629
+ with self.session() as session:
630
+ return (
631
+ session.query(ArchivedMessage)
632
+ .filter(ArchivedMessage.room_id == room_pk)
633
+ .filter(ArchivedMessage.legacy_id == legacy_id)
634
+ .first()
635
+ )
636
+
548
637
 
549
638
  class MultiStore(EngineMixin):
550
639
  def get_xmpp_ids(self, user_pk: int, xmpp_id: str) -> list[str]:
@@ -676,27 +765,13 @@ class RoomStore(UpdatedMixin):
676
765
  super().__init__(*a, **kw)
677
766
  with self.session() as session:
678
767
  session.execute(
679
- update(Room).values(subject_setter_id=None, user_resources=None)
768
+ update(Room).values(
769
+ subject_setter=None, user_resources=None, history_filled=False
770
+ )
680
771
  )
681
772
  session.commit()
682
773
 
683
- def add(self, user_pk: int, legacy_id: str, jid: JID) -> int:
684
- if jid.resource:
685
- raise TypeError
686
- with self.session() as session:
687
- existing = session.execute(
688
- select(Room.id)
689
- .where(Room.user_account_id == user_pk)
690
- .where(Room.legacy_id == legacy_id)
691
- ).scalar()
692
- if existing is not None:
693
- return existing
694
- room = Room(jid=jid, user_account_id=user_pk, legacy_id=legacy_id)
695
- session.add(room)
696
- session.commit()
697
- return room.id
698
-
699
- def set_avatar(self, room_pk: int, avatar_pk: int) -> None:
774
+ def set_avatar(self, room_pk: int, avatar_pk: int | None) -> None:
700
775
  with self.session() as session:
701
776
  session.execute(
702
777
  update(Room).where(Room.id == room_pk).values(avatar_id=avatar_pk)
@@ -728,43 +803,45 @@ class RoomStore(UpdatedMixin):
728
803
  .where(Room.legacy_id == legacy_id)
729
804
  ).scalar()
730
805
 
731
- def update(self, room: "LegacyMUC"):
732
- from slidge.contact import LegacyContact
733
-
806
+ def update_subject_setter(self, room_pk: int, subject_setter: str | None):
734
807
  with self.session() as session:
735
- if room.subject_setter is None:
736
- subject_setter_id = None
737
- elif isinstance(room.subject_setter, str):
738
- subject_setter_id = None
739
- elif isinstance(room.subject_setter, LegacyContact):
740
- subject_setter_id = None
741
- elif room.subject_setter.is_system:
742
- subject_setter_id = None
743
- else:
744
- subject_setter_id = room.subject_setter.pk
745
-
746
808
  session.execute(
747
809
  update(Room)
748
- .where(Room.id == room.pk)
749
- .values(
750
- updated=True,
751
- extra_attributes=room.serialize_extra_attributes(),
752
- name=room.name,
753
- description=room.description,
754
- user_resources=(
755
- None
756
- if not room._user_resources
757
- else json.dumps(list(room._user_resources))
758
- ),
759
- muc_type=room.type,
760
- subject=room.subject,
761
- subject_date=room.subject_date,
762
- subject_setter_id=subject_setter_id,
763
- participants_filled=room._participants_filled,
764
- n_participants=room._n_participants,
810
+ .where(Room.id == room_pk)
811
+ .values(subject_setter=subject_setter)
812
+ )
813
+ session.commit()
814
+
815
+ def update(self, muc: "LegacyMUC") -> int:
816
+ with self.session() as session:
817
+ if muc.pk is None:
818
+ row = Room(
819
+ jid=muc.jid,
820
+ legacy_id=str(muc.legacy_id),
821
+ user_account_id=muc.user_pk,
765
822
  )
823
+ else:
824
+ row = session.query(Room).filter(Room.id == muc.pk).one()
825
+
826
+ row.updated = True
827
+ row.extra_attributes = muc.serialize_extra_attributes()
828
+ row.name = muc.name
829
+ row.description = muc.description
830
+ row.user_resources = (
831
+ None
832
+ if not muc._user_resources
833
+ else json.dumps(list(muc._user_resources))
766
834
  )
835
+ row.muc_type = muc.type
836
+ row.subject = muc.subject
837
+ row.subject_date = muc.subject_date
838
+ row.subject_setter = muc.subject_setter
839
+ row.participants_filled = muc._participants_filled
840
+ row.n_participants = muc._n_participants
841
+ row.user_nick = muc.user_nick
842
+ session.add(row)
767
843
  session.commit()
844
+ return row.id
768
845
 
769
846
  def update_subject_date(
770
847
  self, room_pk: int, subject_date: Optional[datetime]
@@ -789,6 +866,11 @@ class RoomStore(UpdatedMixin):
789
866
  )
790
867
  session.commit()
791
868
 
869
+ def update_name(self, room_pk: int, name: Optional[str]) -> None:
870
+ with self.session() as session:
871
+ session.execute(update(Room).where(Room.id == room_pk).values(name=name))
872
+ session.commit()
873
+
792
874
  def update_n_participants(self, room_pk: int, n: Optional[int]) -> None:
793
875
  with self.session() as session:
794
876
  session.execute(
@@ -826,6 +908,20 @@ class RoomStore(UpdatedMixin):
826
908
  is None
827
909
  )
828
910
 
911
+ def set_participants_filled(self, room_pk: int, val=True) -> None:
912
+ with self.session() as session:
913
+ session.execute(
914
+ update(Room).where(Room.id == room_pk).values(participants_filled=val)
915
+ )
916
+ session.commit()
917
+
918
+ def set_history_filled(self, room_pk: int, val=True) -> None:
919
+ with self.session() as session:
920
+ session.execute(
921
+ update(Room).where(Room.id == room_pk).values(history_filled=True)
922
+ )
923
+ session.commit()
924
+
829
925
  def get_all(self, user_pk: int) -> Iterator[Room]:
830
926
  with self.session() as session:
831
927
  yield from session.execute(
@@ -971,6 +1067,12 @@ class ParticipantStore(EngineMixin):
971
1067
  with self.session() as session:
972
1068
  session.execute(delete(Participant).where(Participant.id == participant_pk))
973
1069
 
1070
+ def get_count(self, room_pk: int) -> int:
1071
+ with self.session() as session:
1072
+ return session.query(
1073
+ count(Participant.id).filter(Participant.room_id == room_pk)
1074
+ ).scalar()
1075
+
974
1076
 
975
1077
  log = logging.getLogger(__name__)
976
1078
  _session: Optional[Session] = None
slidge/group/archive.py CHANGED
@@ -6,8 +6,10 @@ from typing import TYPE_CHECKING, Collection, Optional
6
6
 
7
7
  from slixmpp import Iq, Message
8
8
 
9
+ from ..db.models import ArchivedMessage, ArchivedMessageSource
9
10
  from ..db.store import MAMStore
10
11
  from ..util.archive_msg import HistoryMessage
12
+ from ..util.types import HoleBound
11
13
 
12
14
  if TYPE_CHECKING:
13
15
  from .participant import LegacyParticipant
@@ -22,12 +24,16 @@ class MessageArchive:
22
24
  self,
23
25
  msg: Message,
24
26
  participant: Optional["LegacyParticipant"] = None,
27
+ archive_only=False,
28
+ legacy_msg_id=None,
25
29
  ):
26
30
  """
27
31
  Add a message to the archive if it is deemed archivable
28
32
 
29
33
  :param msg:
30
34
  :param participant:
35
+ :param archive_only:
36
+ :param legacy_msg_id:
31
37
  """
32
38
  if not archivable(msg):
33
39
  return
@@ -47,11 +53,50 @@ class MessageArchive:
47
53
  "jid"
48
54
  ] = f"{uuid.uuid4()}@{participant.xmpp.boundjid.bare}"
49
55
 
50
- self.__store.add_message(self.room_pk, HistoryMessage(new_msg))
56
+ self.__store.add_message(
57
+ self.room_pk,
58
+ HistoryMessage(new_msg),
59
+ archive_only,
60
+ None if legacy_msg_id is None else str(legacy_msg_id),
61
+ )
51
62
 
52
63
  def __iter__(self):
53
64
  return iter(self.get_all())
54
65
 
66
+ @staticmethod
67
+ def __to_bound(stored: ArchivedMessage):
68
+ return HoleBound(
69
+ stored.legacy_id, # type:ignore
70
+ stored.timestamp.replace(tzinfo=timezone.utc),
71
+ )
72
+
73
+ def get_hole_bounds(self) -> tuple[HoleBound | None, HoleBound | None]:
74
+ most_recent = self.__store.get_most_recent_with_legacy_id(self.room_pk)
75
+ if most_recent is None:
76
+ return None, None
77
+ if most_recent.source == ArchivedMessageSource.BACKFILL:
78
+ # most recent = only backfill, fetch everything since last backfill
79
+ return self.__to_bound(most_recent), None
80
+
81
+ most_recent_back_filled = self.__store.get_most_recent_with_legacy_id(
82
+ self.room_pk, ArchivedMessageSource.BACKFILL
83
+ )
84
+ if most_recent_back_filled is None:
85
+ # group was never back-filled, fetch everything before first live
86
+ least_recent_live = self.__store.get_first(self.room_pk, True)
87
+ assert least_recent_live is not None
88
+ return None, self.__to_bound(least_recent_live)
89
+
90
+ assert most_recent_back_filled.legacy_id is not None
91
+ least_recent_live = self.__store.get_least_recent_with_legacy_id_after(
92
+ self.room_pk, most_recent_back_filled.legacy_id
93
+ )
94
+ assert least_recent_live is not None
95
+ # this is a hole caused by slidge downtime
96
+ return self.__to_bound(most_recent_back_filled), self.__to_bound(
97
+ least_recent_live
98
+ )
99
+
55
100
  def get_all(
56
101
  self,
57
102
  start_date: Optional[datetime] = None,
slidge/group/bookmarks.py CHANGED
@@ -3,6 +3,7 @@ import logging
3
3
  from typing import TYPE_CHECKING, Generic, Iterator, Optional, Type
4
4
 
5
5
  from slixmpp import JID
6
+ from slixmpp.exceptions import XMPPError
6
7
  from slixmpp.jid import _unescape_node
7
8
 
8
9
  from ..contact.roster import ESCAPE_TABLE
@@ -57,15 +58,26 @@ class LegacyBookmarks(
57
58
  return f"<Bookmarks of {self.user_jid}>"
58
59
 
59
60
  async def __finish_init_muc(self, legacy_id: LegacyGroupIdType, jid: JID):
60
- muc = self._muc_class(self.session, legacy_id=legacy_id, jid=jid)
61
61
  with self.__store.session():
62
- muc.pk = self.__store.add(self.session.user_pk, str(muc.legacy_id), muc.jid)
63
- muc.archive = MessageArchive(muc.pk, self.xmpp.store.mam)
64
- await muc.avatar_wrap_update_info()
62
+ stored = self.__store.get_by_legacy_id(self.session.user_pk, str(legacy_id))
63
+ if stored is not None:
64
+ if stored.updated:
65
+ return self._muc_class.from_store(self.session, stored)
66
+ muc = self._muc_class(self.session, legacy_id=legacy_id, jid=jid)
67
+ muc.pk = stored.id
68
+ else:
69
+ muc = self._muc_class(self.session, legacy_id=legacy_id, jid=jid)
70
+
71
+ try:
72
+ with muc.updating_info():
73
+ await muc.avatar_wrap_update_info()
74
+ except Exception as e:
75
+ raise XMPPError("internal-server-error", str(e))
65
76
  if not muc.user_nick:
66
77
  muc.user_nick = self._user_nick
67
78
  self.log.debug("MUC created: %r", muc)
68
- self.__store.update(muc)
79
+ muc.pk = self.__store.update(muc)
80
+ muc.archive = MessageArchive(muc.pk, self.xmpp.store.mam)
69
81
  return muc
70
82
 
71
83
  async def legacy_id_to_jid_local_part(self, legacy_id: LegacyGroupIdType):
@@ -117,6 +117,8 @@ class LegacyParticipant(
117
117
  if self._affiliation == affiliation:
118
118
  return
119
119
  self._affiliation = affiliation
120
+ if not self.muc._participants_filled:
121
+ return
120
122
  self.__part_store.set_affiliation(self.pk, affiliation)
121
123
  if not self._presence_sent:
122
124
  return
@@ -145,6 +147,8 @@ class LegacyParticipant(
145
147
  if self._role == role:
146
148
  return
147
149
  self._role = role
150
+ if not self.muc._participants_filled:
151
+ return
148
152
  self.__part_store.set_role(self.pk, role)
149
153
  if not self._presence_sent:
150
154
  return
@@ -154,6 +158,8 @@ class LegacyParticipant(
154
158
  if self._hats == hats:
155
159
  return
156
160
  self._hats = hats
161
+ if not self.muc._participants_filled:
162
+ return
157
163
  self.__part_store.set_hats(self.pk, hats)
158
164
  if not self._presence_sent:
159
165
  return
@@ -313,6 +319,7 @@ class LegacyParticipant(
313
319
  stanza: MessageOrPresenceTypeVar,
314
320
  full_jid: Optional[JID] = None,
315
321
  archive_only=False,
322
+ legacy_msg_id=None,
316
323
  **send_kwargs,
317
324
  ) -> MessageOrPresenceTypeVar:
318
325
  stanza["occupant-id"]["id"] = self.__occupant_id
@@ -331,8 +338,8 @@ class LegacyParticipant(
331
338
  else:
332
339
  stanza.send()
333
340
  else:
334
- if isinstance(stanza, Message):
335
- self.muc.archive.add(stanza, self)
341
+ if hasattr(self.muc, "archive") and isinstance(stanza, Message):
342
+ self.muc.archive.add(stanza, self, archive_only, legacy_msg_id)
336
343
  if archive_only:
337
344
  return stanza
338
345
  for user_full_jid in self.muc.user_full_jids():
@@ -460,7 +467,7 @@ class LegacyParticipant(
460
467
  ):
461
468
  if update_muc:
462
469
  self.muc._subject = subject # type: ignore
463
- self.muc.subject_setter = self
470
+ self.muc.subject_setter = self.nickname
464
471
  self.muc.subject_date = when
465
472
 
466
473
  msg = self._make_message()