From 6b79f180046719ff2ce4b0e8c21b1e4e714bb5ae Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Mon, 2 Nov 2026 23:55:31 +0100 Subject: [PATCH] fix(sync): don't return the mailbox as new messages from the initial sync When a mailbox isn't cached yet, the client syncs it with init set and no known ids (initializeCache() in Mailbox.vue), then loads the first page from the message list. The initial sync returned every message of the mailbox as new anyway, and ran each one through the preview enhancer. On an INBOX of 33,000 messages that request ran out of memory, and the client retried it. The initial sync now only fills the cache and returns the mailbox stats. Partial syncs are unchanged. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Dillard Blom --- lib/Service/Sync/SyncService.php | 8 +++ tests/Unit/Service/Sync/SyncServiceTest.php | 58 +++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/lib/Service/Sync/SyncService.php b/lib/Service/Sync/SyncService.php index 2d1f75a697..c683ddb4a0 100644 --- a/lib/Service/Sync/SyncService.php +++ b/lib/Service/Sync/SyncService.php @@ -104,6 +104,14 @@ public function syncMailbox(Account $account, !$partialOnly, ); + if (!$partialOnly) { + // The initial sync fills the cache of a mailbox the client has not + // loaded yet; the client loads the first page from the message list + // right after. Returning the cache as new messages here would send + // every message of the mailbox through the preview enhancer. + return new Response([], [], [], $mailbox->getStats()); + } + $query = $filter === null ? null : $this->filterStringParser->parse($filter); return $this->getDatabaseSyncChanges( $account, diff --git a/tests/Unit/Service/Sync/SyncServiceTest.php b/tests/Unit/Service/Sync/SyncServiceTest.php index 24c31b17d1..fe94a56b8f 100644 --- a/tests/Unit/Service/Sync/SyncServiceTest.php +++ b/tests/Unit/Service/Sync/SyncServiceTest.php @@ -133,4 +133,62 @@ public function testSyncMailboxReturnsFolderStats(): void { $this->assertEquals($expectedResponse, $response); } + + public function testInitialSyncReturnsNoMessages(): void { + $account = $this->createMock(Account::class); + $account->method('getUserId')->willReturn('user'); + $mailbox = new Mailbox(); + $mailbox->setMessages(33000); + $mailbox->setUnseen(24000); + $this->messageConnector->expects($this->once()) + ->method('syncMailbox') + ->willReturn(new \OCA\Mail\Protocol\SyncResult()); + // The client loads the first page itself, so the cache is not read + $this->messageMapper->expects($this->never()) + ->method('findAllIds'); + $this->messageMapper->expects($this->never()) + ->method('findNewIds'); + $this->messageMapper->expects($this->never()) + ->method('findByMailboxAndIds'); + + $response = $this->syncService->syncMailbox( + $account, + $mailbox, + 0, + false, + null, + [] + ); + + $this->assertEquals(new Response([], [], [], new MailboxStats(33000, 24000, null)), $response); + } + + public function testPartialSyncReadsTheCache(): void { + $account = $this->createMock(Account::class); + $account->method('getUserId')->willReturn('user'); + $mailbox = new Mailbox(); + $mailbox->setMessages(3); + $mailbox->setUnseen(1); + $mailbox->setSyncNewToken('token'); + $mailbox->setSyncChangedToken('token'); + $mailbox->setSyncVanishedToken('token'); + $this->messageConnector->expects($this->once()) + ->method('syncMailbox') + ->willReturn(new \OCA\Mail\Protocol\SyncResult()); + $this->messageMapper->expects($this->once()) + ->method('findNewIds') + ->with($mailbox, [1, 2], null, 'DESC') + ->willReturn([3]); + $this->messageMapper->method('findByMailboxAndIds') + ->willReturn([]); + + $this->syncService->syncMailbox( + $account, + $mailbox, + 0, + true, + null, + [1, 2] + ); + } }