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
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()