Skip to content

Commit

Permalink
feat(cardav): support result truncation for addressbook federation
Browse files Browse the repository at this point in the history
Signed-off-by: Hamza Mahjoubi <[email protected]>
  • Loading branch information
hamza221 committed Jan 10, 2025
1 parent d4ce307 commit 1cb4891
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 17 deletions.
3 changes: 0 additions & 3 deletions apps/dav/lib/CardDAV/AddressBook.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
46 changes: 41 additions & 5 deletions apps/dav/lib/CardDAV/CardDavBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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();

Expand All @@ -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;
Expand Down
8 changes: 7 additions & 1 deletion apps/dav/lib/CardDAV/SyncService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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));

Check failure on line 203 in apps/dav/lib/CardDAV/SyncService.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidScalarArgument

apps/dav/lib/CardDAV/SyncService.php:203:47: InvalidScalarArgument: Argument 2 of DOMDocument::createElement expects string, but int provided (see https://psalm.dev/012)
$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();
Expand Down
9 changes: 1 addition & 8 deletions apps/dav/lib/CardDAV/SystemAddressbook.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,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
Expand Down
8 changes: 8 additions & 0 deletions lib/public/Http/Client/IClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 1cb4891

Please sign in to comment.