Fence Mail provider and mailbox recovery effects

This commit is contained in:
2026-08-03 05:00:19 +02:00
parent ff3aa066ae
commit 5b5077cde7
13 changed files with 1339 additions and 63 deletions
+84 -19
View File
@@ -89,6 +89,11 @@ from govoplan_mail.backend.mail_profiles import (
)
from govoplan_mail.backend.config import ImapConfig, SmtpConfig
from govoplan_mail.backend.runtime import get_registry
from govoplan_mail.backend.recovery import (
MailRecoveryError,
MailboxRefreshBusy,
begin_mailbox_refresh_recovery,
)
from govoplan_mail.backend.delivery_outbox import (
MailDeliveryError,
delivery_command_diagnostics,
@@ -2639,10 +2644,30 @@ def list_profile_mailbox_folders(
detected_sent_folder=None,
)
return _mailbox_folder_response(result, from_cache=True, refreshing=False, indexed_at=cached.indexed_at)
result = list_imap_folders(imap_config=imap, include_status=include_status)
cache_mailbox_folders(session, tenant_id=principal.tenant_id, profile_id=profile_id, result=result)
session.commit()
recovery = begin_mailbox_refresh_recovery(
tenant_id=principal.tenant_id,
profile_id=profile_id,
folder="*",
purpose="folders",
)
try:
result = list_imap_folders(imap_config=imap, include_status=include_status)
cache_mailbox_folders(session, tenant_id=principal.tenant_id, profile_id=profile_id, result=result)
session.commit()
except Exception as exc:
session.rollback()
if not recovery.operation.closed:
recovery.reject(
summary="The read-only IMAP folder refresh did not commit",
code=exc.__class__.__name__,
)
raise
recovery.complete_folders(result)
return _mailbox_folder_response(result)
except MailboxRefreshBusy as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
except MailRecoveryError as exc:
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(exc)) from exc
except MailProfileError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
except ImapConfigurationError as exc:
@@ -2724,16 +2749,32 @@ def bootstrap_profile_mailbox(
),
)
result = load_imap_mailbox_bootstrap(
imap_config=imap,
recovery = begin_mailbox_refresh_recovery(
tenant_id=principal.tenant_id,
profile_id=profile_id,
folder=folder,
limit=limit,
offset=offset,
include_folder_status=include_status,
purpose="bootstrap",
)
cache_mailbox_folders(session, tenant_id=principal.tenant_id, profile_id=profile_id, result=result.folders)
cache_mailbox_messages(session, tenant_id=principal.tenant_id, profile_id=profile_id, result=result.messages)
session.commit()
try:
result = load_imap_mailbox_bootstrap(
imap_config=imap,
folder=folder,
limit=limit,
offset=offset,
include_folder_status=include_status,
)
cache_mailbox_folders(session, tenant_id=principal.tenant_id, profile_id=profile_id, result=result.folders)
cache_mailbox_messages(session, tenant_id=principal.tenant_id, profile_id=profile_id, result=result.messages)
session.commit()
except Exception as exc:
session.rollback()
if not recovery.operation.closed:
recovery.reject(
summary="The read-only IMAP bootstrap did not commit",
code=exc.__class__.__name__,
)
raise
recovery.complete_bootstrap(result.folders, result.messages)
next_cursor, cursor_stable = _next_mailbox_cursor(
tenant_id=principal.tenant_id,
profile_id=profile_id,
@@ -2764,6 +2805,10 @@ def bootstrap_profile_mailbox(
messages=result.messages.messages,
),
)
except MailboxRefreshBusy as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
except MailRecoveryError as exc:
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(exc)) from exc
except MailProfileError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
except ImapConfigurationError as exc:
@@ -2830,16 +2875,32 @@ def list_profile_mailbox_messages(
refreshing=False,
indexed_at=cached.indexed_at,
)
result = list_imap_messages(
imap_config=imap,
recovery = begin_mailbox_refresh_recovery(
tenant_id=principal.tenant_id,
profile_id=profile_id,
folder=folder,
limit=effective_limit,
offset=effective_offset,
after_uid=after_uid,
expected_uidvalidity=expected_uidvalidity,
purpose="messages",
)
cache_mailbox_messages(session, tenant_id=principal.tenant_id, profile_id=profile_id, result=result)
session.commit()
try:
result = list_imap_messages(
imap_config=imap,
folder=folder,
limit=effective_limit,
offset=effective_offset,
after_uid=after_uid,
expected_uidvalidity=expected_uidvalidity,
)
cache_mailbox_messages(session, tenant_id=principal.tenant_id, profile_id=profile_id, result=result)
session.commit()
except Exception as exc:
session.rollback()
if not recovery.operation.closed:
recovery.reject(
summary="The read-only IMAP message refresh did not commit",
code=exc.__class__.__name__,
)
raise
recovery.complete_messages(result)
full = bool(cursor_values is not None and result.cursor_reset)
next_cursor, cursor_stable = _next_mailbox_cursor(
tenant_id=principal.tenant_id,
@@ -2866,6 +2927,10 @@ def list_profile_mailbox_messages(
full=full or not cursor_stable,
messages=result.messages,
)
except MailboxRefreshBusy as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
except MailRecoveryError as exc:
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(exc)) from exc
except MailProfileError as exc:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
except ImapConfigurationError as exc: