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 749ecac575
commit 4204a8ee1d
3 changed files with 57 additions and 13 deletions

View file

@ -478,6 +478,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,13 +66,11 @@ class SyncService extends ASyncService {
throw $ex;
}
$received = [];
// 3. apply changes
// TODO: use multi-get for download
foreach ($response['response'] as $resource => $status) {
$cardUri = basename($resource);
if (isset($status[200])) {
$received[] = $cardUri;
$absoluteUrl = $this->prepareUri($url, $resource);
$vCard = $this->download($absoluteUrl, $userName, $sharedSecret);
$this->atomic(function () use ($addressBookId, $cardUri, $vCard): void {
@ -88,15 +86,6 @@ class SyncService extends ASyncService {
}
}
// 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'],
@ -225,4 +214,15 @@ class SyncService extends ASyncService {
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 {