Skip to content

Commit a9cc4b9

Browse files
author
Ivan
committed
feat: fix auto-resend functionality with attachment handling and improve duplicate message cleanup logic
1 parent bce42bc commit a9cc4b9

7 files changed

Lines changed: 425 additions & 36 deletions

File tree

meshchatx.rsm

111 Bytes
Binary file not shown.

meshchatx/meshchat.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,10 @@
183183
RECENT_SAME_CONTENT_SECONDS,
184184
AutoResendCoordinator,
185185
cooldown_until,
186+
fields_have_attachments,
186187
fields_with_auto_resend_count,
187188
next_attempt_count,
189+
parse_fields_dict,
188190
should_skip_for_budget,
189191
)
190192
from meshchatx.src.backend.local_message_retention import (
@@ -23244,6 +23246,16 @@ async def resend_failed_messages_for_destination(
2324423246
):
2324523247
continue
2324623248

23249+
fields = parse_fields_dict(failed_message.get("fields"))
23250+
allow_attachments = (
23251+
ctx.config.allow_auto_resending_failed_messages_with_attachments.get()
23252+
)
23253+
if not allow_attachments and fields_have_attachments(fields):
23254+
print(
23255+
"Not resending failed message with attachments, as setting is disabled",
23256+
)
23257+
continue
23258+
2324723259
claimed = (
2324823260
ctx.database.messages.try_claim_failed_message_for_auto_resend(
2324923261
message_hash,
@@ -23257,53 +23269,40 @@ async def resend_failed_messages_for_destination(
2325723269
if not claimed:
2325823270
continue
2325923271

23260-
# parse fields as json
23261-
fields = json.loads(failed_message["fields"] or "{}")
23262-
if not isinstance(fields, dict):
23263-
fields = {}
23264-
2326523272
# parse image field
2326623273
image_field = None
23267-
if "image" in fields:
23274+
if "image" in fields and isinstance(fields.get("image"), dict):
2326823275
image_field = LxmfImageField(
2326923276
fields["image"]["image_type"],
2327023277
base64.b64decode(fields["image"]["image_bytes"]),
2327123278
)
2327223279

2327323280
# parse audio field
2327423281
audio_field = None
23275-
if "audio" in fields:
23282+
if "audio" in fields and isinstance(fields.get("audio"), dict):
2327623283
audio_field = LxmfAudioField(
2327723284
fields["audio"]["audio_mode"],
2327823285
base64.b64decode(fields["audio"]["audio_bytes"]),
2327923286
)
2328023287

2328123288
# parse file attachments field
2328223289
file_attachments_field = None
23283-
if "file_attachments" in fields:
23290+
if "file_attachments" in fields and isinstance(
23291+
fields.get("file_attachments"),
23292+
list,
23293+
):
2328423294
file_attachments = [
2328523295
LxmfFileAttachment(
2328623296
file_attachment["file_name"],
2328723297
base64.b64decode(file_attachment["file_bytes"]),
2328823298
)
2328923299
for file_attachment in fields["file_attachments"]
23300+
if isinstance(file_attachment, dict)
2329023301
]
2329123302
file_attachments_field = LxmfFileAttachmentsField(
2329223303
file_attachments,
2329323304
)
2329423305

23295-
# don't resend message with attachments if not allowed
23296-
if not ctx.config.allow_auto_resending_failed_messages_with_attachments.get():
23297-
if (
23298-
image_field is not None
23299-
or audio_field is not None
23300-
or file_attachments_field is not None
23301-
):
23302-
print(
23303-
"Not resending failed message with attachments, as setting is disabled",
23304-
)
23305-
continue
23306-
2330723306
attempt = next_attempt_count(failed_message.get("fields"))
2330823307
ctx.database.messages.set_message_fields_json(
2330923308
message_hash,

meshchatx/src/backend/auto_resend_guard.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ def lock_for(self, identity_key: str, destination_hash: str) -> asyncio.Lock:
6767
return lock
6868

6969

70+
def fields_have_attachments(fields_raw: Any) -> bool:
71+
fields = _parse_fields(fields_raw)
72+
if not fields:
73+
return False
74+
if fields.get("image") or fields.get("audio"):
75+
return True
76+
files = fields.get("file_attachments")
77+
return isinstance(files, list) and len(files) > 0
78+
79+
80+
def parse_fields_dict(fields_raw: Any) -> dict:
81+
return _parse_fields(fields_raw)
82+
83+
7084
def should_skip_for_budget(
7185
fields_raw: Any, *, max_attempts: int = MAX_AUTO_RESEND_ATTEMPTS
7286
) -> bool:

meshchatx/src/backend/database/messages.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -852,26 +852,38 @@ def list_duplicate_lxmf_message_hashes_by_content(self) -> list[str]:
852852
"""Hashes of duplicate rows (same peer, direction, and text), excluding the oldest keep.
853853
854854
Empty or whitespace-only content is ignored so attachment-only or blank
855-
rows are not collapsed together.
855+
rows are not collapsed together. Oldest is by timestamp then id.
856856
"""
857-
rows = self.provider.fetchall(
857+
groups = self.provider.fetchall(
858858
"""
859-
SELECT m.hash AS hash
860-
FROM lxmf_messages m
861-
INNER JOIN (
862-
SELECT peer_hash, is_incoming, content, MIN(id) AS keep_id
863-
FROM lxmf_messages
864-
WHERE content IS NOT NULL AND TRIM(content) != ''
865-
GROUP BY peer_hash, is_incoming, content
866-
HAVING COUNT(*) > 1
867-
) d
868-
ON m.peer_hash = d.peer_hash
869-
AND m.is_incoming = d.is_incoming
870-
AND m.content = d.content
871-
WHERE m.id != d.keep_id
859+
SELECT peer_hash, is_incoming, content
860+
FROM lxmf_messages
861+
WHERE content IS NOT NULL AND TRIM(content) != ''
862+
GROUP BY peer_hash, is_incoming, content
863+
HAVING COUNT(*) > 1
872864
""",
873865
)
874-
return [r["hash"] for r in rows if r.get("hash")]
866+
to_delete: list[str] = []
867+
for group in groups:
868+
rows = self.provider.fetchall(
869+
"""
870+
SELECT hash
871+
FROM lxmf_messages
872+
WHERE peer_hash = ?
873+
AND is_incoming = ?
874+
AND content = ?
875+
ORDER BY
876+
CASE WHEN timestamp IS NULL THEN 1 ELSE 0 END,
877+
timestamp ASC,
878+
id ASC
879+
""",
880+
(group["peer_hash"], group["is_incoming"], group["content"]),
881+
)
882+
# Keep the first (oldest); delete the rest.
883+
for row in rows[1:]:
884+
if row.get("hash"):
885+
to_delete.append(row["hash"])
886+
return to_delete
875887

876888
def count_duplicate_lxmf_messages_by_content(self) -> int:
877889
return len(self.list_duplicate_lxmf_message_hashes_by_content())

0 commit comments

Comments
 (0)