diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index 4829bdf4e76..07738053b46 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -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(); @@ -1533,4 +1540,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); + } } diff --git a/apps/dav/lib/CardDAV/SyncService.php b/apps/dav/lib/CardDAV/SyncService.php index 0bc5c57a406..38c2cef83a5 100644 --- a/apps/dav/lib/CardDAV/SyncService.php +++ b/apps/dav/lib/CardDAV/SyncService.php @@ -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']); + } + } } diff --git a/apps/federation/lib/SyncFederationAddressBooks.php b/apps/federation/lib/SyncFederationAddressBooks.php index e78a98835e0..b3272597110 100644 --- a/apps/federation/lib/SyncFederationAddressBooks.php +++ b/apps/federation/lib/SyncFederationAddressBooks.php @@ -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 {