Make thread/channel/message deletion complete (hard delete)
Deleting a thread, channel, or message was a no-op/soft-delete: messages were only dropped from the plugin outbox (still in hermes' session store, hence searchable/recoverable) and channels/threads were merely archived. Now deletion is complete and non-recoverable, with no search trace: - purge.py (new): hard-delete from hermes' session store (state.db). delete_lane wipes a channel's/thread's sessions + messages; deleting a messages row also drops it from the FTS5 index via the delete triggers. delete_message removes one message, matched by (session, role, exact content, closest timestamp) since plugin m_<hex> ids aren't persisted. - channels.py: delete() hard-deletes the row (and a channel's child threads) instead of archiving. - outbox.py: add delete_lane() (wipe all frames for a lane) and message_info() (read a message's final role/text/ts for the match). - adapter.py: on_channel_delete wipes outbox + session store; on_message_delete purges the session-store row per message. - App: delete confirmations no longer claim history stays for search; ChannelStore removes a channel's threads on channel delete. - Docs updated to describe hard deletion.
This commit is contained in:
1 parent
9286937e2d
commit
17bf41a0b9
11 files changed
+373
-51
No files matched your search
@@ -114,6 +114,7 @@ from hermes_constants import get_hermes_home # noqa: E402
|
||||
|
||||
from . import media as media_bridge # noqa: E402
|
||||
from . import protocol # noqa: E402
|
||||
from . import purge as purge_bridge # noqa: E402
|
||||
from . import search as search_bridge # noqa: E402
|
||||
from .channels import get_directory # noqa: E402
|
||||
from .outbox import Outbox # noqa: E402
|
||||
@@ -2519,6 +2520,28 @@ class AndroidAdapter(BasePlatformAdapter):
|
||||
),
|
||||
)
|
||||
return
|
||||
# Complete deletion: wipe the lane's history from the outbox (so
|
||||
# ``history`` / ``sync`` can't resurrect it) and from the hermes
|
||||
# session store (so no search trace survives). A channel delete takes
|
||||
# its threads with it (thread_id=None); a thread delete is scoped to
|
||||
# its parent channel + thread_id.
|
||||
if entry.get("kind") == "thread":
|
||||
lane_chat_id = entry.get("parent_chat_id") or chat_id
|
||||
thread_id = chat_id
|
||||
else:
|
||||
lane_chat_id = chat_id
|
||||
thread_id = None
|
||||
removed_frames = self._outbox.delete_lane(lane_chat_id, thread_id=thread_id)
|
||||
removed_msgs = purge_bridge.delete_lane(
|
||||
get_hermes_home() / "state.db", lane_chat_id, thread_id=thread_id
|
||||
)
|
||||
logger.info(
|
||||
"android: channel.delete %s kind=%s outbox_frames=%s session_msgs=%s",
|
||||
chat_id,
|
||||
entry.get("kind"),
|
||||
removed_frames,
|
||||
removed_msgs,
|
||||
)
|
||||
resp = protocol.channel_deleted(chat_id)
|
||||
resp.id = frame.id
|
||||
await self._ws_server.broadcast(resp)
|
||||
@@ -2663,13 +2686,14 @@ class AndroidAdapter(BasePlatformAdapter):
|
||||
async def on_message_delete(self, frame: protocol.Frame, device_id: str) -> None:
|
||||
"""Handle an inbound ``message.delete`` request.
|
||||
|
||||
Removes the requested message(s) from the outbox (so ``history`` and
|
||||
``sync`` no longer return them) and broadcasts ``message.deleted`` to
|
||||
every device (outboxed too, so an offline device learns of the
|
||||
deletion on its next ``sync``). Deleting is idempotent: a message that
|
||||
is already gone (pruned by retention) simply yields 0 removed rows,
|
||||
and the ``message.deleted`` broadcast is still emitted so live caches
|
||||
drop it.
|
||||
Completely deletes the requested message(s): they are removed from the
|
||||
outbox (so ``history`` and ``sync`` no longer return them) **and** from
|
||||
the hermes session store (so no search trace survives and they are not
|
||||
recoverable). ``message.deleted`` is broadcast to every device
|
||||
(outboxed too, so an offline device learns of the deletion on its next
|
||||
``sync``). Deleting is idempotent: a message that is already gone
|
||||
(pruned by retention) simply yields 0 removed rows, and the
|
||||
``message.deleted`` broadcast is still emitted so live caches drop it.
|
||||
"""
|
||||
payload = frame.payload
|
||||
chat_id = frame.chat_id or payload.get("chat_id")
|
||||
@@ -2698,15 +2722,30 @@ class AndroidAdapter(BasePlatformAdapter):
|
||||
)
|
||||
return
|
||||
removed = 0
|
||||
purged = 0
|
||||
db_path = get_hermes_home() / "state.db"
|
||||
for mid in message_ids:
|
||||
# Read the final frame data first (role / text / ts) so the
|
||||
# session-store row can be matched, then drop the outbox frames.
|
||||
info = self._outbox.message_info(chat_id, mid, thread_id=thread_id)
|
||||
removed += self._outbox.delete_message(chat_id, mid, thread_id=thread_id)
|
||||
if info:
|
||||
purged += purge_bridge.delete_message(
|
||||
db_path,
|
||||
chat_id,
|
||||
thread_id,
|
||||
info.get("role") or "",
|
||||
info.get("text") or "",
|
||||
info.get("ts"),
|
||||
)
|
||||
logger.info(
|
||||
"android: message.delete from %s chat_id=%r thread_id=%r ids=%s removed=%s",
|
||||
"android: message.delete from %s chat_id=%r thread_id=%r ids=%s removed=%s purged=%s",
|
||||
device_id,
|
||||
chat_id,
|
||||
thread_id,
|
||||
message_ids,
|
||||
removed,
|
||||
purged,
|
||||
)
|
||||
resp = protocol.message_deleted(chat_id, message_ids, thread_id=thread_id)
|
||||
resp.id = frame.id
|
||||
|
||||
Reference in new issue
Block a user