feat(sync): idempotent email import so retries do not duplicate IMAP (#85)#86
Conversation
🔍 CI ResultsOverall: ❌ 12/12 lanes failed TL;DR: ❌ Quality issues: PHPUnit: 54 tests failed; PHPStan: 328 unique errors in 8 lanes; PHP-CS-Fixer: 24 files. Summary by PHP Version
Quality Metrics
❌ Failed Lanesphp8.0-dev
php8.0-stable
php8.1-dev
php8.1-stable
php8.2-dev
php8.2-stable
php8.3-dev
php8.3-stable
php8.4-dev
php8.4-stable
php8.5-dev
php8.5-stable
CI powered by horde-components • View full results |
747a9b8 to
b12f517
Compare
Record applied Add/Modify/Remove (and remove-as-move) under the current SyncKey so a lost Sync response can be retried without appending another generation of Draft messages. Dual-write email Add clientids into the sync map; map Draft Modify old→new UIDs and suppress mirror Add/Delete export via mailmap. Closes #85.
b12f517 to
b7f383e
Compare
Guard importMessageDeletion mailmap checks to CLASS_EMAIL so Notes (and other PIM) UUID server ids are not queried against the integer mailmap.message_uid column. Reject non-integer UIDs in isMailMapChangeApplied and roll back an open DB transaction on query failure to avoid PostgreSQL 22P02/25P02 cascades during Sync.
|
@ralflang bitte reviewen und mergen |
IMAP append+delete gives an edited Draft a new UID, but MS-ASCMD Sync Responses must reference the ServerId the client sent. Returning the new UID made Gmail reject the response and re-send the whole Drafts batch under the old SyncKey. _draftModifyStat() now returns the client ServerId as 'id' (the new UID stays in the applied map and mailmap and still feeds conversationindex). Also fall back to a synthetic conversationid for drafts without a subject, since empty conversation data suppresses the Modify reply entirely.
|
Follow-up from the latest reporter log bundle: idempotent retry now works (78 duplicate Modifies, 0.0s import), but Gmail still refused the new SyncKey because |
Release version 3.2.0 Merge pull request #91 from horde/feat/sync-performance docs(activesync): move #88 entry to recently completed in todo.md feat(activesync): cut steady-state polling and export cost (#88) feat(sync): evict ghost items via deferred synthetic deletions refactor(activesync): centralize client quirks in Device::hasQuirk() feat(activesync): persist folder UID map across FolderSync resets (#87) feat(sync): idempotent email import so retries do not duplicate IMAP (#85) (#86)
Summary
Add/Modify/Remove(including remove-as-move) idempotent under the same SyncKey so a lost or rejected Sync response cannot duplicate IMAP messagesModify(UID replace via append+delete) records old→new mapping and returns the same successful reply on retryMotivation
Tracked in #85 (observed with Gmail Android
FullDraftsUpSync). Streaming / transport work (#84) reduces client read timeouts but does not fix the durability mismatch: client commands are committed to IMAP before the client acks the response. On retry under SyncKey N, a non-idempotent Draft Modify appends another generation of messages.Complementary to #84 — not stacked on it; this PR targets
FRAMEWORK_6_0directly.Changes
State_Base/State_Sql/State_Mongo:getAppliedPIMChange,recordAppliedPIMChange,recordPIMAddition,isMailMapChangeApplied, mail-move mapping; tightenisDuplicatePIMAddition; MongoCHANGE_TYPE_DRAFT+isDuplicatePIMChangeConnector_Importer: short-circuit retries; Draft Modify dual-writes map then mailmap (sync_draftnew +sync_deletedold); rebuild stable conversation fields for EAS 16 SyncRepliesImporterIdempotentImportTestRelated Core PR: refuse Draft Modify append-as-new when the old IMAP UID is already missing (defense in depth).
Test plan
vendor/bin/phpunit … ImporterIdempotentImportTest.php(6 tests)FullDraftsUpSyncagainst a small Drafts folder does not grow across failed/partial responses