fix: fix full addressbook sync with truncated results

Signed-off-by: Robin Appelman <robin@icewind.nl>
This commit is contained in:
Robin Appelman 2026-04-07 17:59:35 +02:00
parent af0069c00e
commit e87645a78a
No known key found for this signature in database
GPG key ID: 42B69D8A64526EFB
3 changed files with 57 additions and 12 deletions

View file

@ -479,6 +479,13 @@ class CardDavBackend implements BackendInterface, SyncSupport {
->from($this->dbCardsTable)
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressbookId)));
return $this->getCardsFromQuery($query);
}
/**
* @return array[]
*/
private function getCardsFromQuery(IQueryBuilder $query): array {
$cards = [];
$result = $query->executeQuery();
@ -1532,4 +1539,32 @@ class CardDavBackend implements BackendInterface, SyncSupport {
// should already be handled, but just in case
throw new BadRequest('vCard can not be empty');
}
/**
* Mark all cards in an address book as needing to be validated
*
* This is done by setting the modified date to `null`, once a sync runs
* the mtime will be set to a non-null value. Leaving all deleted items with
* a null modified date.
*/
public function markCardsAsPending(int $addressBookId): void {
$query = $this->db->getQueryBuilder();
$query->update($this->dbCardsTable)
->set('lastmodified', $query->createNamedParameter(null))
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
->executeStatement();
}
/**
* @return array[]
*/
public function getPendingCards(int $addressBookId): array {
$query = $this->db->getQueryBuilder();
$query->select(['id', 'addressbookid', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid'])
->from($this->dbCardsTable)
->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId)))
->andWhere($query->expr()->isNull('lastmodified'));
return $this->getCardsFromQuery($query);
}
}

View file

@ -66,7 +66,6 @@ class SyncService {
throw $ex;
}
$received = [];
// 3. apply changes
// TODO: use multi-get for download
foreach ($response['response'] as $resource => $status) {
@ -86,15 +85,6 @@ class SyncService {
}
}
// when doing a full sync, remove any items in the local address book that aren't in the remote one
if (!$syncToken) {
$existingCards = $this->backend->getCards($addressBookId);
$removedCards = array_filter($existingCards, fn (array $card) => !in_array($card['uri'], $received));
foreach ($removedCards as $removedCard) {
$this->backend->deleteCard($addressBookId, $removedCard['uri']);
}
}
return [
$response['token'],
$response['truncated'],
@ -365,4 +355,15 @@ class SyncService {
public static function getCardUri(IUser $user): string {
return $user->getBackendClassName() . ':' . $user->getUID() . '.vcf';
}
public function markCardsAsPending(int $addressBookId): void {
$this->backend->markCardsAsPending($addressBookId);
}
public function deletePendingCards(int $addressBookId): void {
$cards = $this->backend->getPendingCards($addressBookId);
foreach ($cards as $card) {
$this->backend->deleteCard($addressBookId, $card['uri']);
}
}
}

View file

@ -51,7 +51,12 @@ class SyncFederationAddressBooks {
];
try {
$syncToken = $oldSyncToken;
$syncToken = $full ? null : $oldSyncToken;
$book = $this->syncService->ensureSystemAddressBookExists($targetPrincipal, $targetBookId, $targetBookProperties);
if ($full) {
$this->syncService->markCardsAsPending($book['id']);
}
do {
[$syncToken, $truncated] = $this->syncService->syncRemoteAddressBook(
@ -59,13 +64,17 @@ class SyncFederationAddressBooks {
$cardDavUser,
$addressBookUrl,
$sharedSecret,
$full ? null : $syncToken,
$syncToken,
$targetBookId,
$targetPrincipal,
$targetBookProperties
);
} while ($truncated);
if ($full) {
$this->syncService->deletePendingCards($book['id']);
}
if ($syncToken !== $oldSyncToken) {
$this->dbHandler->setServerStatus($url, TrustedServers::STATUS_OK, $syncToken);
} else {