Refactor calendar outbox delivery paths

This commit is contained in:
2026-07-21 12:30:03 +02:00
parent 760a87ab99
commit 30f6d79f9e

View File

@@ -858,15 +858,18 @@ def _fail_operation(
session.flush()
def execute_calendar_outbox_operation(
def _lock_leased_outbox_operation(
session: Session,
*,
operation_id: str,
lease_token: str,
client_factory: Callable[[Session, CalendarSyncSource], object] | None = None,
) -> CalendarOutboxOperation:
) -> tuple[CalendarOutboxOperation, CalendarSyncSource | None]:
operation = session.get(CalendarOutboxOperation, operation_id)
if operation is None or operation.status != "in_progress" or operation.lease_token != lease_token:
if (
operation is None
or operation.status != "in_progress"
or operation.lease_token != lease_token
):
raise ValueError("Calendar outbox lease is no longer valid")
source = (
session.query(CalendarSyncSource)
@@ -882,36 +885,86 @@ def execute_calendar_outbox_operation(
)
if operation.status != "in_progress" or operation.lease_token != lease_token:
raise ValueError("Calendar outbox lease is no longer valid")
operation_metadata = operation.metadata_ or {}
unavailable_reason = _operation_source_unavailable_reason(operation, source)
if unavailable_reason:
return operation, source
def _cancel_undeliverable_operation(
session: Session,
*,
operation: CalendarOutboxOperation,
source: CalendarSyncSource | None,
reason: str,
) -> None:
if source is not None and source.tenant_id == operation.tenant_id:
_fail_operation(
session,
operation=operation,
source=source,
error=unavailable_reason,
error=reason,
terminal_status="cancelled",
)
else:
return
operation.status = "cancelled"
operation.completed_at = utcnow()
operation.last_error = unavailable_reason
operation.last_error = reason
operation.lease_token = None
operation.lease_expires_at = None
session.flush()
return operation
overwrite = bool(operation_metadata.get("overwrite")) if isinstance(operation_metadata, dict) else False
client: object | None = None
try:
if client_factory is None:
def _calendar_outbox_client(
session: Session,
*,
source: CalendarSyncSource,
client_factory: Callable[[Session, CalendarSyncSource], object] | None,
) -> object:
if client_factory is not None:
return client_factory(session, source)
from govoplan_calendar.backend.service import caldav_client_for_source
client = caldav_client_for_source(session, source)
else:
client = client_factory(session, source)
if operation.operation_kind == "put":
return caldav_client_for_source(session, source)
def _try_remote_match(
client: object,
operation: CalendarOutboxOperation,
) -> tuple[bool, str | None] | None:
try:
return _remote_matches_operation(client, operation)
except Exception:
return None
def _complete_if_remote_match_is_usable(
session: Session,
*,
operation: CalendarOutboxOperation,
source: CalendarSyncSource,
remote_match: tuple[bool, str | None] | None,
) -> bool:
if remote_match is None:
return False
matched, remote_etag = remote_match
if not matched or (operation.operation_kind != "delete" and not remote_etag):
return False
_complete_operation(
session,
operation=operation,
source=source,
remote_etag=remote_etag,
reconciled=True,
)
return True
def _execute_calendar_outbox_put(
session: Session,
*,
operation: CalendarOutboxOperation,
source: CalendarSyncSource,
client: object,
overwrite: bool,
) -> None:
result = client.put_object(
operation.resource_href,
operation.payload_ics or "",
@@ -920,41 +973,48 @@ def execute_calendar_outbox_operation(
overwrite=overwrite,
)
remote_etag = result.etag
reconciled = False
if not remote_etag:
try:
matched, remote_etag = _remote_matches_operation(client, operation)
except Exception:
matched, remote_etag = False, None
if not matched or not remote_etag:
if remote_etag:
_complete_operation(
session,
operation=operation,
source=source,
remote_etag=remote_etag,
reconciled=False,
)
return
remote_match = _try_remote_match(client, operation)
if _complete_if_remote_match_is_usable(
session,
operation=operation,
source=source,
remote_match=remote_match,
):
return
_fail_operation(
session,
operation=operation,
source=source,
error="CalDAV PUT succeeded without an ETag and could not be reconciled safely",
)
return operation
reconciled = True
_complete_operation(
session,
operation=operation,
source=source,
remote_etag=remote_etag,
reconciled=reconciled,
)
return operation
def _execute_calendar_outbox_delete(
session: Session,
*,
operation: CalendarOutboxOperation,
source: CalendarSyncSource,
client: object,
overwrite: bool,
) -> None:
if not operation.expected_etag and not overwrite:
matched, remote_etag = _remote_matches_operation(client, operation)
if matched:
_complete_operation(
remote_match = _remote_matches_operation(client, operation)
if _complete_if_remote_match_is_usable(
session,
operation=operation,
source=source,
remote_etag=remote_etag,
reconciled=True,
)
return operation
remote_match=remote_match,
):
return
_fail_operation(
session,
operation=operation,
@@ -965,7 +1025,7 @@ def execute_calendar_outbox_operation(
),
terminal_status="conflict",
)
return operation
return
result = client.delete_object(
operation.resource_href,
etag=operation.expected_etag,
@@ -978,25 +1038,28 @@ def execute_calendar_outbox_operation(
remote_etag=result.etag,
reconciled=result.status == 404,
)
return operation
except CalDAVPreconditionFailed as exc:
if client is None:
_fail_operation(session, operation=operation, source=source, error=exc)
return operation
try:
matched, remote_etag = _remote_matches_operation(client, operation)
except Exception:
_fail_operation(session, operation=operation, source=source, error=exc)
else:
if matched and (operation.operation_kind == "delete" or remote_etag):
_complete_operation(
def _handle_outbox_precondition_failure(
session: Session,
*,
operation: CalendarOutboxOperation,
source: CalendarSyncSource,
client: object | None,
error: CalDAVPreconditionFailed,
) -> None:
remote_match = _try_remote_match(client, operation) if client is not None else None
if _complete_if_remote_match_is_usable(
session,
operation=operation,
source=source,
remote_etag=remote_etag,
reconciled=True,
)
else:
remote_match=remote_match,
):
return
if remote_match is None:
_fail_operation(session, operation=operation, source=source, error=error)
return
matched, _remote_etag = remote_match
_fail_operation(
session,
operation=operation,
@@ -1008,24 +1071,95 @@ def execute_calendar_outbox_operation(
),
terminal_status=None if matched else "conflict",
)
return operation
except Exception as exc:
matched, remote_etag = False, None
if client is not None:
try:
matched, remote_etag = _remote_matches_operation(client, operation)
except Exception:
matched, remote_etag = False, None
if matched and (operation.operation_kind == "delete" or remote_etag):
_complete_operation(
def _handle_outbox_delivery_failure(
session: Session,
*,
operation: CalendarOutboxOperation,
source: CalendarSyncSource,
client: object | None,
error: Exception,
) -> None:
remote_match = _try_remote_match(client, operation) if client is not None else None
if _complete_if_remote_match_is_usable(
session,
operation=operation,
source=source,
remote_etag=remote_etag,
reconciled=True,
remote_match=remote_match,
):
return
_fail_operation(session, operation=operation, source=source, error=error)
def execute_calendar_outbox_operation(
session: Session,
*,
operation_id: str,
lease_token: str,
client_factory: Callable[[Session, CalendarSyncSource], object] | None = None,
) -> CalendarOutboxOperation:
operation, source = _lock_leased_outbox_operation(
session,
operation_id=operation_id,
lease_token=lease_token,
)
unavailable_reason = _operation_source_unavailable_reason(operation, source)
if unavailable_reason:
_cancel_undeliverable_operation(
session,
operation=operation,
source=source,
reason=unavailable_reason,
)
return operation
assert source is not None
operation_metadata = operation.metadata_ or {}
overwrite = (
bool(operation_metadata.get("overwrite"))
if isinstance(operation_metadata, dict)
else False
)
client: object | None = None
try:
client = _calendar_outbox_client(
session,
source=source,
client_factory=client_factory,
)
if operation.operation_kind == "put":
_execute_calendar_outbox_put(
session,
operation=operation,
source=source,
client=client,
overwrite=overwrite,
)
else:
_fail_operation(session, operation=operation, source=source, error=exc)
_execute_calendar_outbox_delete(
session,
operation=operation,
source=source,
client=client,
overwrite=overwrite,
)
except CalDAVPreconditionFailed as exc:
_handle_outbox_precondition_failure(
session,
operation=operation,
source=source,
client=client,
error=exc,
)
except Exception as exc:
_handle_outbox_delivery_failure(
session,
operation=operation,
source=source,
client=client,
error=exc,
)
return operation