From c642e5d0dcbd68a6dee09deba8ef45f1e8f4535e Mon Sep 17 00:00:00 2001 From: Hamza Mahjoubi Date: Wed, 8 Jan 2025 20:39:18 +0700 Subject: [PATCH 1/5] feat(cardav): support result truncation for addressbook federation Signed-off-by: Hamza Mahjoubi --- apps/dav/lib/CardDAV/AddressBook.php | 3 -- apps/dav/lib/CardDAV/CardDavBackend.php | 46 +++++++++++++++++++--- apps/dav/lib/CardDAV/SyncService.php | 8 +++- apps/dav/lib/CardDAV/SystemAddressbook.php | 9 +---- config/config.sample.php | 5 +++ lib/public/Http/Client/IClient.php | 8 ++++ 6 files changed, 62 insertions(+), 17 deletions(-) diff --git a/apps/dav/lib/CardDAV/AddressBook.php b/apps/dav/lib/CardDAV/AddressBook.php index 2ec645f04d231..0691ace9b3705 100644 --- a/apps/dav/lib/CardDAV/AddressBook.php +++ b/apps/dav/lib/CardDAV/AddressBook.php @@ -234,9 +234,6 @@ private function canWrite(): bool { } public function getChanges($syncToken, $syncLevel, $limit = null) { - if (!$syncToken && $limit) { - throw new UnsupportedLimitOnInitialSyncException(); - } return parent::getChanges($syncToken, $syncLevel, $limit); } diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index b15ed60707685..daad251d77899 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -873,8 +873,29 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, 'modified' => [], 'deleted' => [], ]; - - if ($syncToken) { + if(str_starts_with($syncToken, "init_")) { + $syncValues = explode("_", $syncToken); + $lastID = $syncValues[1]; + $initialSyncToken = $syncValues[2]; + $qb = $this->db->getQueryBuilder(); + $qb->select('id','uri') + ->from('cards') + ->where( + $qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId)) + )->setMaxResults($limit); + $stmt = $qb->executeQuery(); + $values = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR); + $lastID = array_key_last($values); + $result['syncToken'] = 'init_'. $lastID.'_'.$initialSyncToken; + $result['added'] = array_values($values); + $stmt->closeCursor(); + $result['result_truncated'] = true; + if (count($result['added']) < $limit ) { + $result['syncToken'] = $initialSyncToken; + $result['result_truncated'] = false; + } + } + else if ($syncToken) { $qb = $this->db->getQueryBuilder(); $qb->select('uri', 'operation') ->from('addressbookchanges') @@ -899,6 +920,8 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, // last change on a node is relevant. while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $changes[$row['uri']] = $row['operation']; + // get the last synctoken, needed in case a limit was set + $result['syncToken'] = $row['synctoken']; } $stmt->closeCursor(); @@ -917,14 +940,27 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, } } else { $qb = $this->db->getQueryBuilder(); - $qb->select('uri') + $qb->select('id','uri') ->from('cards') ->where( $qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId)) ); // No synctoken supplied, this is the initial sync. - $stmt = $qb->executeQuery(); - $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN); + if (is_int($limit) && $limit > 0) { + $qb->setMaxResults($limit); + $stmt = $qb->executeQuery(); + $values = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR); + $lastID = array_key_last($values); + if(count(array_values($values)) === $limit ){ + $result['syncToken'] = 'init_'. $lastID.'_'.$currentToken; + $result['result_truncated'] = true; + } + } + else { + $stmt = $qb->executeQuery(); + } + $result['added'] = array_values($values); + $stmt->closeCursor(); } return $result; diff --git a/apps/dav/lib/CardDAV/SyncService.php b/apps/dav/lib/CardDAV/SyncService.php index cc3d324faf1ff..ff84c0fb486c1 100644 --- a/apps/dav/lib/CardDAV/SyncService.php +++ b/apps/dav/lib/CardDAV/SyncService.php @@ -162,7 +162,7 @@ protected function requestSyncReport(string $url, string $userName, string $addr 'auth' => [$userName, $sharedSecret], 'body' => $this->buildSyncCollectionRequestBody($syncToken), 'headers' => ['Content-Type' => 'application/xml'], - 'timeout' => $this->config->getSystemValueInt('carddav_sync_request_timeout', IClient::DEFAULT_REQUEST_TIMEOUT) + 'timeout' => $this->config->getSystemValueInt('carddav_sync_request_timeout', IClient::DEFAULT_REQUEST_TIMEOUT), ]; $response = $client->request( @@ -194,17 +194,23 @@ protected function download(string $url, string $userName, string $sharedSecret, } private function buildSyncCollectionRequestBody(?string $syncToken): string { + $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $root = $dom->createElementNS('DAV:', 'd:sync-collection'); $sync = $dom->createElement('d:sync-token', $syncToken ?? ''); + $limit = $dom->createElement('d:limit'); + $nresuts = $dom->createElement('d:nresults',$this->config->getSystemValueInt('carddav_sync_request_limit',IClient::DEFAULT_ADDRESSBOOK_INITIAL_SYNC_LIMIT)); + $limit->appendChild($nresuts); $prop = $dom->createElement('d:prop'); $cont = $dom->createElement('d:getcontenttype'); $etag = $dom->createElement('d:getetag'); + $prop->appendChild($cont); $prop->appendChild($etag); $root->appendChild($sync); + $root->appendChild($limit); $root->appendChild($prop); $dom->appendChild($root); return $dom->saveXML(); diff --git a/apps/dav/lib/CardDAV/SystemAddressbook.php b/apps/dav/lib/CardDAV/SystemAddressbook.php index e0032044e701d..ee066bf97d7c8 100644 --- a/apps/dav/lib/CardDAV/SystemAddressbook.php +++ b/apps/dav/lib/CardDAV/SystemAddressbook.php @@ -211,15 +211,8 @@ public function getChild($name): Card { $obj['carddata'] = $carddata; } return new Card($this->carddavBackend, $this->addressBookInfo, $obj); - } - - /** - * @throws UnsupportedLimitOnInitialSyncException - */ + } public function getChanges($syncToken, $syncLevel, $limit = null) { - if (!$syncToken && $limit) { - throw new UnsupportedLimitOnInitialSyncException(); - } if (!$this->carddavBackend instanceof SyncSupport) { return null; diff --git a/config/config.sample.php b/config/config.sample.php index bfda1609d751a..f8cb906a7ada1 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -346,6 +346,11 @@ */ 'carddav_sync_request_timeout' => 30, +/** + * The limit applied to the initial synchronization report request, e.g. federated system address books (as run by `occ federation:sync-addressbooks`). + */ +'carddav_initial_sync_request_limit' => 1000, + /** * `true` enabled a relaxed session timeout, where the session timeout would no longer be * handled by Nextcloud but by either the PHP garbage collection or the expiration of diff --git a/lib/public/Http/Client/IClient.php b/lib/public/Http/Client/IClient.php index e4f46d44e4dfa..800a7aaa69e33 100644 --- a/lib/public/Http/Client/IClient.php +++ b/lib/public/Http/Client/IClient.php @@ -22,6 +22,14 @@ interface IClient { */ public const DEFAULT_REQUEST_TIMEOUT = 30; + /** + * Default limit for address book intial sync + * + * @since 31.0.0 + */ + + public const DEFAULT_ADDRESSBOOK_INITIAL_SYNC_LIMIT = 1; + /** * Sends a GET request * @param string $uri From d656754d719b118ec06b6c43b83504c9db2189a4 Mon Sep 17 00:00:00 2001 From: Hamza Mahjoubi Date: Sun, 12 Jan 2025 15:53:56 +0700 Subject: [PATCH 2/5] fixup! feat(cardav): support result truncation for addressbook federation Signed-off-by: Hamza Mahjoubi --- apps/dav/lib/CardDAV/CardDavBackend.php | 17 ++++++++++++++--- apps/dav/lib/CardDAV/SyncService.php | 9 ++------- apps/dav/lib/CardDAV/SystemAddressbook.php | 2 +- apps/dav/lib/RootCollection.php | 6 ++++++ apps/dav/lib/Server.php | 3 ++- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index daad251d77899..f1237ae70e394 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -21,6 +21,8 @@ use OCA\DAV\Events\CardUpdatedEvent; use OCP\AppFramework\Db\TTransactional; use OCP\DB\Exception; +use Psr\Log\LoggerInterface; +use OCP\IConfig; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\IEventDispatcher; use OCP\IDBConnection; @@ -59,6 +61,8 @@ public function __construct( private IUserManager $userManager, private IEventDispatcher $dispatcher, private Sharing\Backend $sharingBackend, + private LoggerInterface $logger, + private IConfig $config, ) { } @@ -851,6 +855,10 @@ public function deleteCard($addressBookId, $cardUri) { * @return array */ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) { + if($limit === null){ + $limit = $this->config->getSystemValueInt('carddav_sync_request_limit',1); + + } // Current synctoken return $this->atomic(function () use ($addressBookId, $syncToken, $syncLevel, $limit) { $qb = $this->db->getQueryBuilder(); @@ -873,6 +881,7 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, 'modified' => [], 'deleted' => [], ]; + $this->logger->error('getChangesForAddressBook', ['syncToken' => $syncToken, 'currentToken' => $currentToken, 'limit' => $limit]); if(str_starts_with($syncToken, "init_")) { $syncValues = explode("_", $syncToken); $lastID = $syncValues[1]; @@ -949,8 +958,8 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, if (is_int($limit) && $limit > 0) { $qb->setMaxResults($limit); $stmt = $qb->executeQuery(); - $values = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR); - $lastID = array_key_last($values); + $values = $stmt->fetchAll(\PDO::FETCH_ASSOC); + $lastID = end($values)['id']; if(count(array_values($values)) === $limit ){ $result['syncToken'] = 'init_'. $lastID.'_'.$currentToken; $result['result_truncated'] = true; @@ -958,8 +967,10 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, } else { $stmt = $qb->executeQuery(); + $values = $stmt->fetchAll(\PDO::FETCH_ASSOC); + $this->logger->error('getChangesForAddressBook', ['values' => $values]); } - $result['added'] = array_values($values); + $result['added'] =array_column($values, 'uri'); $stmt->closeCursor(); } diff --git a/apps/dav/lib/CardDAV/SyncService.php b/apps/dav/lib/CardDAV/SyncService.php index ff84c0fb486c1..cf597d078a39c 100644 --- a/apps/dav/lib/CardDAV/SyncService.php +++ b/apps/dav/lib/CardDAV/SyncService.php @@ -63,6 +63,7 @@ public function syncRemoteAddressBook(string $url, string $userName, string $add $this->logger->error('Client exception:', ['app' => 'dav', 'exception' => $ex]); throw $ex; } + $this->logger->error('sync response',[$response]); // 3. apply changes // TODO: use multi-get for download @@ -162,7 +163,7 @@ protected function requestSyncReport(string $url, string $userName, string $addr 'auth' => [$userName, $sharedSecret], 'body' => $this->buildSyncCollectionRequestBody($syncToken), 'headers' => ['Content-Type' => 'application/xml'], - 'timeout' => $this->config->getSystemValueInt('carddav_sync_request_timeout', IClient::DEFAULT_REQUEST_TIMEOUT), + 'timeout' => $this->config->getSystemValueInt('carddav_sync_request_timeout', IClient::DEFAULT_REQUEST_TIMEOUT) ]; $response = $client->request( @@ -194,23 +195,17 @@ protected function download(string $url, string $userName, string $sharedSecret, } private function buildSyncCollectionRequestBody(?string $syncToken): string { - $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; $root = $dom->createElementNS('DAV:', 'd:sync-collection'); $sync = $dom->createElement('d:sync-token', $syncToken ?? ''); - $limit = $dom->createElement('d:limit'); - $nresuts = $dom->createElement('d:nresults',$this->config->getSystemValueInt('carddav_sync_request_limit',IClient::DEFAULT_ADDRESSBOOK_INITIAL_SYNC_LIMIT)); - $limit->appendChild($nresuts); $prop = $dom->createElement('d:prop'); $cont = $dom->createElement('d:getcontenttype'); $etag = $dom->createElement('d:getetag'); - $prop->appendChild($cont); $prop->appendChild($etag); $root->appendChild($sync); - $root->appendChild($limit); $root->appendChild($prop); $dom->appendChild($root); return $dom->saveXML(); diff --git a/apps/dav/lib/CardDAV/SystemAddressbook.php b/apps/dav/lib/CardDAV/SystemAddressbook.php index ee066bf97d7c8..4270a87fc4418 100644 --- a/apps/dav/lib/CardDAV/SystemAddressbook.php +++ b/apps/dav/lib/CardDAV/SystemAddressbook.php @@ -211,7 +211,7 @@ public function getChild($name): Card { $obj['carddata'] = $carddata; } return new Card($this->carddavBackend, $this->addressBookInfo, $obj); - } + } public function getChanges($syncToken, $syncLevel, $limit = null) { if (!$this->carddavBackend instanceof SyncSupport) { diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index 751ab17bb7aa6..014c9b192a519 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -124,6 +124,8 @@ public function __construct() { ); $contactsSharingBackend = \OC::$server->get(\OCA\DAV\CardDAV\Sharing\Backend::class); + $logger = \OC::$server->get(\Psr\Log\LoggerInterface::class); + $config = \OC::$server->get(IConfig::class); $pluginManager = new PluginManager(\OC::$server, \OC::$server->query(IAppManager::class)); $usersCardDavBackend = new CardDavBackend( @@ -132,6 +134,8 @@ public function __construct() { $userManager, $dispatcher, $contactsSharingBackend, + $logger, + $config ); $usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, $pluginManager, $userSession->getUser(), $groupManager, 'principals/users'); $usersAddressBookRoot->disableListing = $disableListing; @@ -142,6 +146,8 @@ public function __construct() { $userManager, $dispatcher, $contactsSharingBackend, + $logger, + $config ); $systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, $pluginManager, $userSession->getUser(), $groupManager, 'principals/system'); $systemAddressBookRoot->disableListing = $disableListing; diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index f07927ff0f95b..2ed316d7ee240 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -160,7 +160,8 @@ public function __construct( $this->server->addPlugin(new ExceptionLoggerPlugin('webdav', $logger)); $this->server->addPlugin(new LockPlugin()); - $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin()); + $logger = \OC::$server->get(LoggerInterface::class); + $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin($logger)); // acl $acl = new DavAclPlugin(); From 3b8b6c8e181f6b0a8d9abff0bdafdb2a783260a5 Mon Sep 17 00:00:00 2001 From: Hamza Mahjoubi Date: Tue, 14 Jan 2025 17:27:35 +0700 Subject: [PATCH 3/5] fixup! feat(cardav): support result truncation for addressbook federation Signed-off-by: Hamza Mahjoubi --- apps/dav/lib/CardDAV/AddressBook.php | 1 - apps/dav/lib/CardDAV/CardDavBackend.php | 53 +++++++++++----------- apps/dav/lib/CardDAV/SyncService.php | 2 +- apps/dav/lib/CardDAV/SystemAddressbook.php | 1 - 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/apps/dav/lib/CardDAV/AddressBook.php b/apps/dav/lib/CardDAV/AddressBook.php index 0691ace9b3705..bcfd68943ad3c 100644 --- a/apps/dav/lib/CardDAV/AddressBook.php +++ b/apps/dav/lib/CardDAV/AddressBook.php @@ -8,7 +8,6 @@ namespace OCA\DAV\CardDAV; use OCA\DAV\DAV\Sharing\IShareable; -use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException; use OCP\DB\Exception; use OCP\IL10N; use OCP\Server; diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index f1237ae70e394..d886704a3345f 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -21,13 +21,13 @@ use OCA\DAV\Events\CardUpdatedEvent; use OCP\AppFramework\Db\TTransactional; use OCP\DB\Exception; -use Psr\Log\LoggerInterface; -use OCP\IConfig; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\IEventDispatcher; +use OCP\IConfig; use OCP\IDBConnection; use OCP\IUserManager; use PDO; +use Psr\Log\LoggerInterface; use Sabre\CardDAV\Backend\BackendInterface; use Sabre\CardDAV\Backend\SyncSupport; use Sabre\CardDAV\Plugin; @@ -855,8 +855,8 @@ public function deleteCard($addressBookId, $cardUri) { * @return array */ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) { - if($limit === null){ - $limit = $this->config->getSystemValueInt('carddav_sync_request_limit',1); + if ($limit === null) { + $limit = $this->config->getSystemValueInt('carddav_sync_request_limit', 1); } // Current synctoken @@ -882,29 +882,31 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, 'deleted' => [], ]; $this->logger->error('getChangesForAddressBook', ['syncToken' => $syncToken, 'currentToken' => $currentToken, 'limit' => $limit]); - if(str_starts_with($syncToken, "init_")) { - $syncValues = explode("_", $syncToken); + if (str_starts_with($syncToken, 'init_')) { + $syncValues = explode('_', $syncToken); $lastID = $syncValues[1]; $initialSyncToken = $syncValues[2]; $qb = $this->db->getQueryBuilder(); - $qb->select('id','uri') + $qb->select('id', 'uri') ->from('cards') ->where( - $qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId)) - )->setMaxResults($limit); + $qb->expr()->andX( + $qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId)), + $qb->expr()->gt('id', $qb->createNamedParameter($lastID))) + )->orderBy('id') + ->setMaxResults($limit); $stmt = $qb->executeQuery(); - $values = $stmt->fetchAll(\PDO::FETCH_KEY_PAIR); - $lastID = array_key_last($values); - $result['syncToken'] = 'init_'. $lastID.'_'.$initialSyncToken; - $result['added'] = array_values($values); + $values = $stmt->fetchAll(\PDO::FETCH_ASSOC); + $lastID = end($values)['id']; + $result['syncToken'] = 'init_' . $lastID . '_' . $initialSyncToken; + $result['added'] = array_column($values, 'uri'); $stmt->closeCursor(); $result['result_truncated'] = true; - if (count($result['added']) < $limit ) { + if (count($result['added']) < $limit) { $result['syncToken'] = $initialSyncToken; $result['result_truncated'] = false; } - } - else if ($syncToken) { + } elseif ($syncToken) { $qb = $this->db->getQueryBuilder(); $qb->select('uri', 'operation') ->from('addressbookchanges') @@ -929,7 +931,7 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, // last change on a node is relevant. while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $changes[$row['uri']] = $row['operation']; - // get the last synctoken, needed in case a limit was set + // get the last synctoken, needed in case a limit was set $result['syncToken'] = $row['synctoken']; } $stmt->closeCursor(); @@ -949,7 +951,7 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, } } else { $qb = $this->db->getQueryBuilder(); - $qb->select('id','uri') + $qb->select('id', 'uri') ->from('cards') ->where( $qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId)) @@ -958,19 +960,18 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, if (is_int($limit) && $limit > 0) { $qb->setMaxResults($limit); $stmt = $qb->executeQuery(); - $values = $stmt->fetchAll(\PDO::FETCH_ASSOC); + $values = $stmt->fetchAll(\PDO::FETCH_ASSOC); $lastID = end($values)['id']; - if(count(array_values($values)) === $limit ){ - $result['syncToken'] = 'init_'. $lastID.'_'.$currentToken; + if (count(array_values($values)) === $limit) { + $result['syncToken'] = 'init_' . $lastID . '_' . $currentToken; $result['result_truncated'] = true; - } - } - else { + } + } else { $stmt = $qb->executeQuery(); - $values = $stmt->fetchAll(\PDO::FETCH_ASSOC); + $values = $stmt->fetchAll(\PDO::FETCH_ASSOC); $this->logger->error('getChangesForAddressBook', ['values' => $values]); } - $result['added'] =array_column($values, 'uri'); + $result['added'] = array_column($values, 'uri'); $stmt->closeCursor(); } diff --git a/apps/dav/lib/CardDAV/SyncService.php b/apps/dav/lib/CardDAV/SyncService.php index cf597d078a39c..1c99a5ef356bd 100644 --- a/apps/dav/lib/CardDAV/SyncService.php +++ b/apps/dav/lib/CardDAV/SyncService.php @@ -63,7 +63,7 @@ public function syncRemoteAddressBook(string $url, string $userName, string $add $this->logger->error('Client exception:', ['app' => 'dav', 'exception' => $ex]); throw $ex; } - $this->logger->error('sync response',[$response]); + $this->logger->error('sync response', [$response]); // 3. apply changes // TODO: use multi-get for download diff --git a/apps/dav/lib/CardDAV/SystemAddressbook.php b/apps/dav/lib/CardDAV/SystemAddressbook.php index 4270a87fc4418..912a2f1dcee05 100644 --- a/apps/dav/lib/CardDAV/SystemAddressbook.php +++ b/apps/dav/lib/CardDAV/SystemAddressbook.php @@ -8,7 +8,6 @@ */ namespace OCA\DAV\CardDAV; -use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException; use OCA\Federation\TrustedServers; use OCP\Accounts\IAccountManager; use OCP\IConfig; From 71a6837bbc8a8bc42430ee0cddd590648f98b5c8 Mon Sep 17 00:00:00 2001 From: Hamza Mahjoubi Date: Tue, 14 Jan 2025 20:25:21 +0700 Subject: [PATCH 4/5] fixup! feat(cardav): support result truncation for addressbook federation Signed-off-by: Hamza Mahjoubi --- apps/federation/lib/SyncFederationAddressBooks.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/federation/lib/SyncFederationAddressBooks.php b/apps/federation/lib/SyncFederationAddressBooks.php index 05144b40879bf..1c88ccd73dada 100644 --- a/apps/federation/lib/SyncFederationAddressBooks.php +++ b/apps/federation/lib/SyncFederationAddressBooks.php @@ -52,6 +52,9 @@ public function syncThemAll(\Closure $callback) { try { $newToken = $this->syncService->syncRemoteAddressBook($url, $cardDavUser, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetBookProperties); if ($newToken !== $syncToken) { + if (strpos($newToken, 'init') !== false) { + $newToken = $this->syncTruncatedAddressBook($url, $cardDavUser, $addressBookUrl, $sharedSecret, $newToken, $targetBookId, $targetPrincipal, $targetBookProperties); + } $this->dbHandler->setServerStatus($url, TrustedServers::STATUS_OK, $newToken); } else { $this->logger->debug("Sync Token for $url unchanged from previous sync"); @@ -76,4 +79,12 @@ public function syncThemAll(\Closure $callback) { } } } + + private function syncTruncatedAddressBook(string $url, string $cardDavUser, string $addressBookUrl, string $sharedSecret, string $syncToken, int $targetBookId, string $targetPrincipal, array $targetBookProperties): string { + $newToken = $this->syncService->syncRemoteAddressBook($url, $cardDavUser, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetBookProperties); + while(strpos($newToken, 'init') !== false) { + $newToken = $this->syncService->syncRemoteAddressBook($url, $cardDavUser, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetBookProperties); + } + return $newToken; + } } From ef45c01381c2f036ef3c433be209ffa47b8066ef Mon Sep 17 00:00:00 2001 From: Hamza Mahjoubi Date: Tue, 21 Jan 2025 20:15:39 +0700 Subject: [PATCH 5/5] fixup! feat(cardav): support result truncation for addressbook federation Signed-off-by: Hamza Mahjoubi --- apps/dav/lib/CardDAV/CardDavBackend.php | 12 +++++++----- apps/federation/lib/SyncFederationAddressBooks.php | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index d886704a3345f..404b3042bf1e9 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -897,14 +897,16 @@ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, ->setMaxResults($limit); $stmt = $qb->executeQuery(); $values = $stmt->fetchAll(\PDO::FETCH_ASSOC); - $lastID = end($values)['id']; - $result['syncToken'] = 'init_' . $lastID . '_' . $initialSyncToken; - $result['added'] = array_column($values, 'uri'); $stmt->closeCursor(); - $result['result_truncated'] = true; - if (count($result['added']) < $limit) { + if (count($values) === 0) { $result['syncToken'] = $initialSyncToken; $result['result_truncated'] = false; + $result['added'] = []; + } else { + $lastID = end($values)['id']; + $result['added'] = array_column($values, 'uri'); + $result['syncToken'] = count($result['added']) === $limit ? "init_{$lastID}_$initialSyncToken" : $initialSyncToken ; + $result['result_truncated'] = count($result['added']) === $limit; } } elseif ($syncToken) { $qb = $this->db->getQueryBuilder(); diff --git a/apps/federation/lib/SyncFederationAddressBooks.php b/apps/federation/lib/SyncFederationAddressBooks.php index 1c88ccd73dada..53931dd1675f9 100644 --- a/apps/federation/lib/SyncFederationAddressBooks.php +++ b/apps/federation/lib/SyncFederationAddressBooks.php @@ -52,6 +52,7 @@ public function syncThemAll(\Closure $callback) { try { $newToken = $this->syncService->syncRemoteAddressBook($url, $cardDavUser, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetBookProperties); if ($newToken !== $syncToken) { + // Finish truncated initial sync. if (strpos($newToken, 'init') !== false) { $newToken = $this->syncTruncatedAddressBook($url, $cardDavUser, $addressBookUrl, $sharedSecret, $newToken, $targetBookId, $targetPrincipal, $targetBookProperties); } @@ -82,7 +83,7 @@ public function syncThemAll(\Closure $callback) { private function syncTruncatedAddressBook(string $url, string $cardDavUser, string $addressBookUrl, string $sharedSecret, string $syncToken, int $targetBookId, string $targetPrincipal, array $targetBookProperties): string { $newToken = $this->syncService->syncRemoteAddressBook($url, $cardDavUser, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetBookProperties); - while(strpos($newToken, 'init') !== false) { + while (strpos($newToken, 'init') !== false) { $newToken = $this->syncService->syncRemoteAddressBook($url, $cardDavUser, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetBookProperties); } return $newToken;