slidge 0.3.0a3__py3-none-any.whl → 0.3.0b1__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 (39) hide show
  1. slidge/contact/contact.py +2 -2
  2. slidge/core/gateway.py +1 -1
  3. slidge/core/mixins/attachment.py +31 -0
  4. slidge/core/mixins/avatar.py +17 -10
  5. slidge/core/session.py +5 -1
  6. slidge/db/alembic/versions/cef02a8b1451_initial_schema.py +361 -0
  7. slidge/group/participant.py +1 -1
  8. slidge/group/room.py +25 -36
  9. slidge/migration.py +14 -5
  10. {slidge-0.3.0a3.dist-info → slidge-0.3.0b1.dist-info}/METADATA +1 -1
  11. {slidge-0.3.0a3.dist-info → slidge-0.3.0b1.dist-info}/RECORD +15 -38
  12. slidge/db/alembic/versions/0337c90c0b96_unify_legacy_xmpp_id_mappings.py +0 -183
  13. slidge/db/alembic/versions/04cf35e3cf85_add_participant_nickname_no_illegal.py +0 -33
  14. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -36
  15. slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +0 -85
  16. slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -36
  17. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -37
  18. slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -41
  19. slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +0 -52
  20. slidge/db/alembic/versions/3231d2c623bc_add_unique_contraint_for_attachment_.py +0 -33
  21. slidge/db/alembic/versions/45c24cc73c91_add_bob.py +0 -42
  22. slidge/db/alembic/versions/4dbd23a3f868_new_avatar_store.py +0 -105
  23. slidge/db/alembic/versions/54ce3cde350c_use_hash_for_avatar_filenames.py +0 -50
  24. slidge/db/alembic/versions/58b98dacf819_refactor.py +0 -118
  25. slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +0 -61
  26. slidge/db/alembic/versions/75a62b74b239_ditch_hats_table.py +0 -74
  27. slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -48
  28. slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +0 -43
  29. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -139
  30. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +0 -50
  31. slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +0 -79
  32. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -214
  33. slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -52
  34. slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -34
  35. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -26
  36. {slidge-0.3.0a3.dist-info → slidge-0.3.0b1.dist-info}/WHEEL +0 -0
  37. {slidge-0.3.0a3.dist-info → slidge-0.3.0b1.dist-info}/entry_points.txt +0 -0
  38. {slidge-0.3.0a3.dist-info → slidge-0.3.0b1.dist-info}/licenses/LICENSE +0 -0
  39. {slidge-0.3.0a3.dist-info → slidge-0.3.0b1.dist-info}/top_level.txt +0 -0
slidge/contact/contact.py CHANGED
@@ -95,7 +95,7 @@ class LegacyContact(
95
95
  super().__init__()
96
96
 
97
97
  @property
98
- def jid(self):
98
+ def jid(self): # type:ignore[override]
99
99
  jid = JID(self.stored.jid)
100
100
  jid.resource = self.RESOURCE
101
101
  return jid
@@ -298,7 +298,7 @@ class LegacyContact(
298
298
  )
299
299
  self.commit()
300
300
  for p in self.participants:
301
- p.nickname = n
301
+ p.nickname = n or str(self.legacy_id)
302
302
 
303
303
  def _post_avatar_update(self, cached_avatar) -> None:
304
304
  if self.is_friend and self.added_to_roster:
slidge/core/gateway.py CHANGED
@@ -472,7 +472,7 @@ class BaseGateway(
472
472
  )
473
473
 
474
474
  @property # type: ignore
475
- def jid(self):
475
+ def jid(self): # type:ignore[override]
476
476
  # Override to avoid slixmpp deprecation warnings.
477
477
  return self.boundjid
478
478
 
@@ -403,6 +403,37 @@ class AttachmentMixin(TextMessageMixin):
403
403
  :param when: when the file was sent, for a "delay" tag (:xep:`0203`)
404
404
  :param thread:
405
405
  """
406
+ coro = self.__send_file(
407
+ attachment,
408
+ legacy_msg_id,
409
+ reply_to=reply_to,
410
+ when=when,
411
+ thread=thread,
412
+ **kwargs,
413
+ )
414
+ if self.session is NotImplemented:
415
+ return await coro
416
+ elif not isinstance(attachment, LegacyAttachment):
417
+ return await coro
418
+ elif attachment.legacy_file_id is None:
419
+ return await coro
420
+ else:
421
+ # prevents race conditions where we download the same thing several time
422
+ # and end up attempting to insert it twice in the DB, raising an
423
+ # IntegrityError.
424
+ async with self.session.lock(("attachment", attachment.legacy_file_id)):
425
+ return await coro
426
+
427
+ async def __send_file(
428
+ self,
429
+ attachment: LegacyAttachment | Path | str,
430
+ legacy_msg_id: Optional[LegacyMessageType] = None,
431
+ *,
432
+ reply_to: Optional[MessageReference] = None,
433
+ when: Optional[datetime] = None,
434
+ thread: Optional[LegacyThreadType] = None,
435
+ **kwargs,
436
+ ) -> tuple[Optional[str], list[Message]]:
406
437
  store_multi = kwargs.pop("store_multi", True)
407
438
  carbon = kwargs.pop("carbon", False)
408
439
  mto = kwargs.pop("mto", None)
@@ -123,19 +123,26 @@ class AvatarMixin(UpdateInfoMixin):
123
123
  else:
124
124
  avatar.path.unlink()
125
125
 
126
- if cached_avatar is None:
127
- self.stored.avatar = None
128
- else:
129
- self.stored.avatar = cached_avatar.stored
126
+ stored_avatar = None if cached_avatar is None else cached_avatar.stored
127
+ self.stored.avatar = stored_avatar
128
+
130
129
  try:
131
130
  self.commit(merge=True)
132
131
  except IntegrityError as e:
133
- self.log.debug(
134
- "Hit integrity error, attempting to fix by refreshing participants"
135
- )
136
- with self.xmpp.store.session() as orm:
137
- orm.refresh(self.stored, ["participants"])
138
- self.commit(merge=True)
132
+ with self.xmpp.store.session(expire_on_commit=False) as orm:
133
+ if orm.object_session(self.stored):
134
+ self.log.debug(
135
+ "Hit integrity error, attempting to fix by refreshing participants"
136
+ )
137
+ orm.refresh(self.stored, ["participants"])
138
+ else:
139
+ self.log.debug(
140
+ "Hit integrity error, attempting to fix by merging contact.stored"
141
+ )
142
+ self.stored = orm.merge(self.stored)
143
+ self.stored.avatar = stored_avatar
144
+ orm.add(self.stored)
145
+ orm.commit()
139
146
 
140
147
  self._post_avatar_update(cached_avatar)
141
148
 
slidge/core/session.py CHANGED
@@ -24,6 +24,7 @@ from ..db.models import Contact, GatewayUser
24
24
  from ..group.bookmarks import LegacyBookmarks
25
25
  from ..group.room import LegacyMUC
26
26
  from ..util import ABCSubclassableOnceAtMost
27
+ from ..util.lock import NamedLockMixin
27
28
  from ..util.types import (
28
29
  LegacyGroupIdType,
29
30
  LegacyMessageType,
@@ -50,7 +51,9 @@ class CachedPresence(NamedTuple):
50
51
 
51
52
 
52
53
  class BaseSession(
53
- Generic[LegacyMessageType, RecipientType], metaclass=ABCSubclassableOnceAtMost
54
+ Generic[LegacyMessageType, RecipientType],
55
+ NamedLockMixin,
56
+ metaclass=ABCSubclassableOnceAtMost,
54
57
  ):
55
58
  """
56
59
  The session of a registered :term:`User`.
@@ -95,6 +98,7 @@ class BaseSession(
95
98
  _bookmarks_cls: Type[LegacyBookmarks]
96
99
 
97
100
  def __init__(self, user: GatewayUser) -> None:
101
+ super().__init__()
98
102
  self.user = user
99
103
  self.log = logging.getLogger(user.jid.bare)
100
104
 
@@ -0,0 +1,361 @@
1
+ """Initial schema
2
+
3
+ Revision ID: cef02a8b1451
4
+ Revises:
5
+ Create Date: 2025-08-28 12:48:16.890606
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ import slidge
15
+
16
+ # revision identifiers, used by Alembic.
17
+ revision: str = "cef02a8b1451"
18
+ down_revision: Union[str, None] = None
19
+ branch_labels: Union[str, Sequence[str], None] = None
20
+ depends_on: Union[str, Sequence[str], None] = None
21
+
22
+
23
+ def upgrade() -> None:
24
+ # ### commands auto generated by Alembic - please adjust! ###
25
+ op.create_table(
26
+ "avatar",
27
+ sa.Column("id", sa.Integer(), nullable=False),
28
+ sa.Column("hash", sa.String(), nullable=False),
29
+ sa.Column("height", sa.Integer(), nullable=False),
30
+ sa.Column("width", sa.Integer(), nullable=False),
31
+ sa.Column("legacy_id", sa.String(), nullable=True),
32
+ sa.Column("url", sa.String(), nullable=True),
33
+ sa.Column("etag", sa.String(), nullable=True),
34
+ sa.Column("last_modified", sa.String(), nullable=True),
35
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_avatar")),
36
+ sa.UniqueConstraint("hash", name=op.f("uq_avatar_hash")),
37
+ sa.UniqueConstraint("legacy_id", name=op.f("uq_avatar_legacy_id")),
38
+ )
39
+ op.create_table(
40
+ "bob",
41
+ sa.Column("id", sa.Integer(), nullable=False),
42
+ sa.Column("file_name", sa.String(), nullable=False),
43
+ sa.Column("sha_1", sa.String(), nullable=False),
44
+ sa.Column("sha_256", sa.String(), nullable=False),
45
+ sa.Column("sha_512", sa.String(), nullable=False),
46
+ sa.Column("content_type", sa.String(), nullable=False),
47
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_bob")),
48
+ sa.UniqueConstraint("sha_1", name=op.f("uq_bob_sha_1")),
49
+ sa.UniqueConstraint("sha_256", name=op.f("uq_bob_sha_256")),
50
+ sa.UniqueConstraint("sha_512", name=op.f("uq_bob_sha_512")),
51
+ )
52
+ op.create_table(
53
+ "user_account",
54
+ sa.Column("id", sa.Integer(), nullable=False),
55
+ sa.Column("jid", slidge.db.meta.JIDType(), nullable=False),
56
+ sa.Column(
57
+ "registration_date",
58
+ sa.DateTime(),
59
+ server_default=sa.text("(CURRENT_TIMESTAMP)"),
60
+ nullable=False,
61
+ ),
62
+ sa.Column(
63
+ "legacy_module_data", slidge.db.meta.JSONEncodedDict(), nullable=False
64
+ ),
65
+ sa.Column("preferences", slidge.db.meta.JSONEncodedDict(), nullable=False),
66
+ sa.Column("avatar_hash", sa.String(), nullable=True),
67
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_user_account")),
68
+ sa.UniqueConstraint("jid", name=op.f("uq_user_account_jid")),
69
+ )
70
+ op.create_table(
71
+ "attachment",
72
+ sa.Column("id", sa.Integer(), nullable=False),
73
+ sa.Column("user_account_id", sa.Integer(), nullable=False),
74
+ sa.Column("legacy_file_id", sa.String(), nullable=True),
75
+ sa.Column("url", sa.String(), nullable=False),
76
+ sa.Column("sims", sa.String(), nullable=True),
77
+ sa.Column("sfs", sa.String(), nullable=True),
78
+ sa.ForeignKeyConstraint(
79
+ ["user_account_id"],
80
+ ["user_account.id"],
81
+ name=op.f("fk_attachment_user_account_id_user_account"),
82
+ ),
83
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_attachment")),
84
+ sa.UniqueConstraint(
85
+ "user_account_id",
86
+ "legacy_file_id",
87
+ name=op.f("uq_attachment_user_account_id"),
88
+ ),
89
+ )
90
+ with op.batch_alter_table("attachment", schema=None) as batch_op:
91
+ batch_op.create_index(
92
+ batch_op.f("ix_attachment_legacy_file_id"), ["legacy_file_id"], unique=False
93
+ )
94
+ batch_op.create_index(batch_op.f("ix_attachment_url"), ["url"], unique=False)
95
+
96
+ op.create_table(
97
+ "contact",
98
+ sa.Column("id", sa.Integer(), nullable=False),
99
+ sa.Column("user_account_id", sa.Integer(), nullable=False),
100
+ sa.Column("legacy_id", sa.String(), nullable=False),
101
+ sa.Column("jid", slidge.db.meta.JIDType(), nullable=False),
102
+ sa.Column("avatar_id", sa.Integer(), nullable=True),
103
+ sa.Column("nick", sa.String(), nullable=True),
104
+ sa.Column("cached_presence", sa.Boolean(), nullable=False),
105
+ sa.Column("last_seen", sa.DateTime(), nullable=True),
106
+ sa.Column("ptype", sa.String(), nullable=True),
107
+ sa.Column("pstatus", sa.String(), nullable=True),
108
+ sa.Column("pshow", sa.String(), nullable=True),
109
+ sa.Column("caps_ver", sa.String(), nullable=True),
110
+ sa.Column("is_friend", sa.Boolean(), nullable=False),
111
+ sa.Column("added_to_roster", sa.Boolean(), nullable=False),
112
+ sa.Column("extra_attributes", slidge.db.meta.JSONEncodedDict(), nullable=True),
113
+ sa.Column("updated", sa.Boolean(), nullable=False),
114
+ sa.Column("vcard", sa.String(), nullable=True),
115
+ sa.Column("vcard_fetched", sa.Boolean(), nullable=False),
116
+ sa.Column(
117
+ "client_type",
118
+ sa.Enum(
119
+ "bot",
120
+ "console",
121
+ "game",
122
+ "handheld",
123
+ "pc",
124
+ "phone",
125
+ "sms",
126
+ "tablet",
127
+ "web",
128
+ native_enum=False,
129
+ ),
130
+ nullable=False,
131
+ ),
132
+ sa.ForeignKeyConstraint(
133
+ ["avatar_id"], ["avatar.id"], name=op.f("fk_contact_avatar_id_avatar")
134
+ ),
135
+ sa.ForeignKeyConstraint(
136
+ ["user_account_id"],
137
+ ["user_account.id"],
138
+ name=op.f("fk_contact_user_account_id_user_account"),
139
+ ),
140
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_contact")),
141
+ sa.UniqueConstraint(
142
+ "user_account_id", "jid", name=op.f("uq_contact_user_account_id")
143
+ ),
144
+ sa.UniqueConstraint(
145
+ "user_account_id", "legacy_id", name=op.f("uq_contact_user_account_id")
146
+ ),
147
+ )
148
+ op.create_table(
149
+ "room",
150
+ sa.Column("id", sa.Integer(), nullable=False),
151
+ sa.Column("user_account_id", sa.Integer(), nullable=False),
152
+ sa.Column("legacy_id", sa.String(), nullable=False),
153
+ sa.Column("jid", slidge.db.meta.JIDType(), nullable=False),
154
+ sa.Column("avatar_id", sa.Integer(), nullable=True),
155
+ sa.Column("name", sa.String(), nullable=True),
156
+ sa.Column("description", sa.String(), nullable=True),
157
+ sa.Column("subject", sa.String(), nullable=True),
158
+ sa.Column("subject_date", sa.DateTime(), nullable=True),
159
+ sa.Column("subject_setter", sa.String(), nullable=True),
160
+ sa.Column("n_participants", sa.Integer(), nullable=True),
161
+ sa.Column(
162
+ "muc_type",
163
+ sa.Enum("GROUP", "CHANNEL", "CHANNEL_NON_ANONYMOUS", name="muctype"),
164
+ nullable=False,
165
+ ),
166
+ sa.Column("user_nick", sa.String(), nullable=True),
167
+ sa.Column("user_resources", sa.String(), nullable=True),
168
+ sa.Column("participants_filled", sa.Boolean(), nullable=False),
169
+ sa.Column("history_filled", sa.Boolean(), nullable=False),
170
+ sa.Column("extra_attributes", slidge.db.meta.JSONEncodedDict(), nullable=True),
171
+ sa.Column("updated", sa.Boolean(), nullable=False),
172
+ sa.ForeignKeyConstraint(
173
+ ["avatar_id"], ["avatar.id"], name=op.f("fk_room_avatar_id_avatar")
174
+ ),
175
+ sa.ForeignKeyConstraint(
176
+ ["user_account_id"],
177
+ ["user_account.id"],
178
+ name=op.f("fk_room_user_account_id_user_account"),
179
+ ),
180
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_room")),
181
+ sa.UniqueConstraint(
182
+ "user_account_id", "jid", name="uq_room_user_account_id_jid"
183
+ ),
184
+ sa.UniqueConstraint(
185
+ "user_account_id", "legacy_id", name="uq_room_user_account_id_legacy_id"
186
+ ),
187
+ )
188
+ op.create_table(
189
+ "contact_sent",
190
+ sa.Column("id", sa.Integer(), nullable=False),
191
+ sa.Column("contact_id", sa.Integer(), nullable=False),
192
+ sa.Column("msg_id", sa.String(), nullable=False),
193
+ sa.ForeignKeyConstraint(
194
+ ["contact_id"],
195
+ ["contact.id"],
196
+ name=op.f("fk_contact_sent_contact_id_contact"),
197
+ ),
198
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_contact_sent")),
199
+ sa.UniqueConstraint(
200
+ "contact_id", "msg_id", name=op.f("uq_contact_sent_contact_id")
201
+ ),
202
+ )
203
+ op.create_table(
204
+ "direct_msg",
205
+ sa.Column("foreign_key", sa.Integer(), nullable=False),
206
+ sa.Column("id", sa.Integer(), nullable=False),
207
+ sa.Column("legacy_id", sa.String(), nullable=False),
208
+ sa.Column("xmpp_id", sa.String(), nullable=False),
209
+ sa.ForeignKeyConstraint(
210
+ ["foreign_key"],
211
+ ["contact.id"],
212
+ name=op.f("fk_direct_msg_foreign_key_contact"),
213
+ ),
214
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_direct_msg")),
215
+ )
216
+ with op.batch_alter_table("direct_msg", schema=None) as batch_op:
217
+ batch_op.create_index(
218
+ "ix_direct_msg_legacy_id", ["legacy_id", "foreign_key"], unique=False
219
+ )
220
+
221
+ op.create_table(
222
+ "direct_thread",
223
+ sa.Column("foreign_key", sa.Integer(), nullable=False),
224
+ sa.Column("id", sa.Integer(), nullable=False),
225
+ sa.Column("legacy_id", sa.String(), nullable=False),
226
+ sa.Column("xmpp_id", sa.String(), nullable=False),
227
+ sa.ForeignKeyConstraint(
228
+ ["foreign_key"],
229
+ ["contact.id"],
230
+ name=op.f("fk_direct_thread_foreign_key_contact"),
231
+ ),
232
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_direct_thread")),
233
+ )
234
+ with op.batch_alter_table("direct_thread", schema=None) as batch_op:
235
+ batch_op.create_index(
236
+ "ix_direct_direct_thread_id", ["legacy_id", "foreign_key"], unique=False
237
+ )
238
+
239
+ op.create_table(
240
+ "group_msg",
241
+ sa.Column("foreign_key", sa.Integer(), nullable=False),
242
+ sa.Column("id", sa.Integer(), nullable=False),
243
+ sa.Column("legacy_id", sa.String(), nullable=False),
244
+ sa.Column("xmpp_id", sa.String(), nullable=False),
245
+ sa.ForeignKeyConstraint(
246
+ ["foreign_key"], ["room.id"], name=op.f("fk_group_msg_foreign_key_room")
247
+ ),
248
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_group_msg")),
249
+ )
250
+ with op.batch_alter_table("group_msg", schema=None) as batch_op:
251
+ batch_op.create_index(
252
+ "ix_group_msg_legacy_id", ["legacy_id", "foreign_key"], unique=False
253
+ )
254
+
255
+ op.create_table(
256
+ "group_thread",
257
+ sa.Column("foreign_key", sa.Integer(), nullable=False),
258
+ sa.Column("id", sa.Integer(), nullable=False),
259
+ sa.Column("legacy_id", sa.String(), nullable=False),
260
+ sa.Column("xmpp_id", sa.String(), nullable=False),
261
+ sa.ForeignKeyConstraint(
262
+ ["foreign_key"], ["room.id"], name=op.f("fk_group_thread_foreign_key_room")
263
+ ),
264
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_group_thread")),
265
+ )
266
+ with op.batch_alter_table("group_thread", schema=None) as batch_op:
267
+ batch_op.create_index(
268
+ "ix_direct_group_thread_id", ["legacy_id", "foreign_key"], unique=False
269
+ )
270
+
271
+ op.create_table(
272
+ "mam",
273
+ sa.Column("id", sa.Integer(), nullable=False),
274
+ sa.Column("room_id", sa.Integer(), nullable=False),
275
+ sa.Column("stanza_id", sa.String(), nullable=False),
276
+ sa.Column("timestamp", sa.DateTime(), nullable=False),
277
+ sa.Column("author_jid", slidge.db.meta.JIDType(), nullable=False),
278
+ sa.Column(
279
+ "source",
280
+ sa.Enum("LIVE", "BACKFILL", name="archivedmessagesource"),
281
+ nullable=False,
282
+ ),
283
+ sa.Column("legacy_id", sa.String(), nullable=True),
284
+ sa.Column("stanza", sa.String(), nullable=False),
285
+ sa.ForeignKeyConstraint(
286
+ ["room_id"], ["room.id"], name=op.f("fk_mam_room_id_room")
287
+ ),
288
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_mam")),
289
+ sa.UniqueConstraint("room_id", "stanza_id", name=op.f("uq_mam_room_id")),
290
+ )
291
+ op.create_table(
292
+ "participant",
293
+ sa.Column("id", sa.Integer(), nullable=False),
294
+ sa.Column("room_id", sa.Integer(), nullable=False),
295
+ sa.Column("contact_id", sa.Integer(), nullable=True),
296
+ sa.Column("is_user", sa.Boolean(), nullable=False),
297
+ sa.Column(
298
+ "affiliation",
299
+ sa.Enum("outcast", "member", "admin", "owner", "none", native_enum=False),
300
+ nullable=False,
301
+ ),
302
+ sa.Column(
303
+ "role",
304
+ sa.Enum("moderator", "participant", "visitor", "none", native_enum=False),
305
+ nullable=False,
306
+ ),
307
+ sa.Column("presence_sent", sa.Boolean(), nullable=False),
308
+ sa.Column("resource", sa.String(), nullable=False),
309
+ sa.Column("nickname", sa.String(), nullable=False),
310
+ sa.Column("nickname_no_illegal", sa.String(), nullable=False),
311
+ sa.Column("hats", sa.JSON(), nullable=False),
312
+ sa.Column("extra_attributes", slidge.db.meta.JSONEncodedDict(), nullable=True),
313
+ sa.ForeignKeyConstraint(
314
+ ["contact_id"],
315
+ ["contact.id"],
316
+ name=op.f("fk_participant_contact_id_contact"),
317
+ ),
318
+ sa.ForeignKeyConstraint(
319
+ ["room_id"], ["room.id"], name=op.f("fk_participant_room_id_room")
320
+ ),
321
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_participant")),
322
+ sa.UniqueConstraint(
323
+ "room_id", "contact_id", name=op.f("uq_participant_room_id")
324
+ ),
325
+ sa.UniqueConstraint("room_id", "resource", name=op.f("uq_participant_room_id")),
326
+ )
327
+ # ### end Alembic commands ###
328
+
329
+
330
+ def downgrade() -> None:
331
+ # ### commands auto generated by Alembic - please adjust! ###
332
+ op.drop_table("participant")
333
+ op.drop_table("mam")
334
+ with op.batch_alter_table("group_thread", schema=None) as batch_op:
335
+ batch_op.drop_index("ix_direct_group_thread_id")
336
+
337
+ op.drop_table("group_thread")
338
+ with op.batch_alter_table("group_msg", schema=None) as batch_op:
339
+ batch_op.drop_index("ix_group_msg_legacy_id")
340
+
341
+ op.drop_table("group_msg")
342
+ with op.batch_alter_table("direct_thread", schema=None) as batch_op:
343
+ batch_op.drop_index("ix_direct_direct_thread_id")
344
+
345
+ op.drop_table("direct_thread")
346
+ with op.batch_alter_table("direct_msg", schema=None) as batch_op:
347
+ batch_op.drop_index("ix_direct_msg_legacy_id")
348
+
349
+ op.drop_table("direct_msg")
350
+ op.drop_table("contact_sent")
351
+ op.drop_table("room")
352
+ op.drop_table("contact")
353
+ with op.batch_alter_table("attachment", schema=None) as batch_op:
354
+ batch_op.drop_index(batch_op.f("ix_attachment_url"))
355
+ batch_op.drop_index(batch_op.f("ix_attachment_legacy_file_id"))
356
+
357
+ op.drop_table("attachment")
358
+ op.drop_table("user_account")
359
+ op.drop_table("bob")
360
+ op.drop_table("avatar")
361
+ # ### end Alembic commands ###
@@ -302,7 +302,7 @@ class LegacyParticipant(
302
302
  return p
303
303
 
304
304
  @property
305
- def DISCO_NAME(self):
305
+ def DISCO_NAME(self): # type:ignore[override]
306
306
  return self.nickname
307
307
 
308
308
  def __send_presence_if_needed(
slidge/group/room.py CHANGED
@@ -18,8 +18,8 @@ from slixmpp.plugins.xep_0469.stanza import NS as PINNING_NS
18
18
  from slixmpp.plugins.xep_0492.stanza import NS as NOTIFY_NS
19
19
  from slixmpp.plugins.xep_0492.stanza import WhenLiteral
20
20
  from slixmpp.xmlstream import ET
21
+ from sqlalchemy.exc import InvalidRequestError
21
22
  from sqlalchemy.orm import Session as OrmSession
22
- from sqlalchemy.orm.exc import DetachedInstanceError
23
23
 
24
24
  from ..contact.contact import LegacyContact
25
25
  from ..contact.roster import ContactIsUser
@@ -247,48 +247,33 @@ class LegacyMUC(
247
247
  return
248
248
  async with self.lock("fill participants"):
249
249
  parts: list[Participant] = []
250
- resources: set[str] = set()
250
+ resources = set[str]()
251
+ # During fill_participants(), self.get_participant*() methods may
252
+ # return a participant with a conflicting nick/resource.
251
253
  async for participant in self.fill_participants():
252
- if participant.stored.id is not None:
254
+ if participant.stored.resource in resources:
255
+ self.log.warning(
256
+ "Participant '%s' was yielded more than once by fill_participants()",
257
+ participant.stored.resource,
258
+ )
253
259
  continue
254
- # During fill_participants(), self.get_participant*() methods may
255
- # return a participant with a conflicting nick/resource. There is
256
- # a better way to fix this than the logic below, but this better way
257
- # has not been found yet.
258
- if participant.jid.resource in resources:
259
- if participant.contact is None:
260
- self.log.warning(
261
- "Ditching participant %s", participant.nickname
262
- )
263
- del participant
264
- continue
265
- else:
266
- nickname = (
267
- f"{participant.nickname} ({participant.contact.jid.node})"
268
- )
269
- participant = self._participant_cls(
270
- self,
271
- Participant(nickname=nickname, room=self.stored),
272
- contact=participant.contact,
273
- )
274
- resources.add(participant.jid.resource)
275
260
  parts.append(participant.stored)
261
+ resources.add(participant.stored.resource)
276
262
  with self.xmpp.store.session(expire_on_commit=False) as orm:
277
- # FIXME: something must be wrong with all these refreshes and merge,
278
- # but I did not manage to get rid of them without getting various
279
- # sqlalchemy exceptions raised everywhere
280
263
  orm.add(self.stored)
281
- orm.refresh(self.stored)
282
- known = {p.resource for p in self.stored.participants}
283
- self.stored.participants_filled = True
264
+ # because self.participants() is async, self.stored may be out of sync at
265
+ # this point.
266
+ with orm.no_autoflush:
267
+ orm.refresh(self.stored)
284
268
  for part in parts:
285
- if part.resource in known:
286
- continue
287
- part = orm.merge(part)
288
- orm.add(part)
289
- self.stored.participants.append(part)
269
+ try:
270
+ self.stored.participants.append(part)
271
+ except InvalidRequestError:
272
+ # the participant was already stored in the DB. `part` may even
273
+ # be out-of-sync, so it's fine to just ditch it.
274
+ pass
275
+ self.stored.participants_filled = True
290
276
  orm.commit()
291
- orm.refresh(self.stored)
292
277
 
293
278
  async def get_participants(
294
279
  self, affiliation: Optional[MucAffiliation] = None
@@ -642,6 +627,10 @@ class LegacyMUC(
642
627
 
643
628
  if user_participant is None:
644
629
  user_participant = await self.get_user_participant()
630
+ with self.xmpp.store.session() as orm:
631
+ orm.add(self.stored)
632
+ with orm.no_autoflush:
633
+ orm.refresh(self.stored, ["participants"])
645
634
  if not user_participant.is_user:
646
635
  self.log.warning("is_user flag not set participant on user_participant")
647
636
  user_participant.is_user = True # type:ignore
slidge/migration.py CHANGED
@@ -1,16 +1,13 @@
1
1
  import logging
2
2
  import shutil
3
3
  import sys
4
+ import traceback
4
5
  from pathlib import Path
5
6
 
6
7
  from alembic import command
7
8
  from alembic.config import Config
8
- from slixmpp import JID
9
9
 
10
10
  from .core import config
11
- from .db.meta import get_engine
12
- from .db.models import GatewayUser
13
- from .db.store import SlidgeStore
14
11
 
15
12
 
16
13
  def remove_avatar_cache_v1() -> None:
@@ -32,7 +29,15 @@ def get_alembic_cfg() -> Config:
32
29
 
33
30
  def migrate() -> None:
34
31
  remove_avatar_cache_v1()
35
- command.upgrade(get_alembic_cfg(), "head")
32
+ try:
33
+ command.upgrade(get_alembic_cfg(), "head")
34
+ except Exception as e:
35
+ traceback.print_exception(e)
36
+ print(
37
+ "Something went wrong during the migration. "
38
+ "This is expected if you upgrade from slidge 0.2, in this case you need to start from a fresh database."
39
+ )
40
+ exit(1)
36
41
 
37
42
 
38
43
  def main() -> None:
@@ -41,6 +46,10 @@ def main() -> None:
41
46
 
42
47
  Usage: python -m slidge.migration "Revision message blah blah blah"
43
48
  """
49
+ dev_db = Path(".") / "dev" / "slidge.sqlite"
50
+ if dev_db.exists():
51
+ # always start from a clean state
52
+ dev_db.unlink()
44
53
  alembic_cfg = get_alembic_cfg()
45
54
  command.upgrade(alembic_cfg, "head")
46
55
  command.revision(alembic_cfg, sys.argv[1], autogenerate=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slidge
3
- Version: 0.3.0a3
3
+ Version: 0.3.0b1
4
4
  Summary: XMPP bridging framework
5
5
  Author-email: Nicolas Cedilnik <nicoco@nicoco.fr>
6
6
  License-Expression: AGPL-3.0-or-later