mirror of
https://github.com/nextcloud/server.git
synced 2026-06-11 09:42:09 -04:00
feat(cardav): support result truncation for addressbook federation
Signed-off-by: Hamza Mahjoubi <hamzamahjoubi221@gmail.com>
This commit is contained in:
parent
dc699a2597
commit
84c0e1be9a
7 changed files with 74 additions and 27 deletions
|
|
@ -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;
|
||||
|
|
@ -234,9 +233,6 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable, IMov
|
|||
}
|
||||
|
||||
public function getChanges($syncToken, $syncLevel, $limit = null) {
|
||||
if (!$syncToken && $limit) {
|
||||
throw new UnsupportedLimitOnInitialSyncException();
|
||||
}
|
||||
|
||||
return parent::getChanges($syncToken, $syncLevel, $limit);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ use OCP\AppFramework\Db\TTransactional;
|
|||
use OCP\DB\Exception;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUserManager;
|
||||
use PDO;
|
||||
|
|
@ -59,6 +60,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
|||
private IUserManager $userManager,
|
||||
private IEventDispatcher $dispatcher,
|
||||
private Sharing\Backend $sharingBackend,
|
||||
private IConfig $config,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -850,6 +852,8 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
|||
* @return array
|
||||
*/
|
||||
public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) {
|
||||
$maxLimit = $this->config->getSystemValueInt('carddav_sync_request_truncation', 2500);
|
||||
$limit = ($limit === null) ? $maxLimit : min($limit, $maxLimit);
|
||||
// Current synctoken
|
||||
return $this->atomic(function () use ($addressBookId, $syncToken, $syncLevel, $limit) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
|
@ -872,10 +876,35 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
|||
'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('uri', 'operation')
|
||||
$qb->select('id', 'uri')
|
||||
->from('cards')
|
||||
->where(
|
||||
$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_ASSOC);
|
||||
$stmt->closeCursor();
|
||||
if (count($values) === 0) {
|
||||
$result['syncToken'] = $initialSyncToken;
|
||||
$result['result_truncated'] = false;
|
||||
$result['added'] = [];
|
||||
} else {
|
||||
$lastID = $values[array_key_last($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();
|
||||
$qb->select('uri', 'operation', 'synctoken')
|
||||
->from('addressbookchanges')
|
||||
->where(
|
||||
$qb->expr()->andX(
|
||||
|
|
@ -885,7 +914,7 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
|||
)
|
||||
)->orderBy('synctoken');
|
||||
|
||||
if (is_int($limit) && $limit > 0) {
|
||||
if ($limit > 0) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
|
||||
|
|
@ -898,8 +927,15 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
|||
// 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();
|
||||
|
||||
// No changes found, use current token
|
||||
if (empty($changes)) {
|
||||
$result['syncToken'] = $currentToken;
|
||||
}
|
||||
|
||||
foreach ($changes as $uri => $operation) {
|
||||
switch ($operation) {
|
||||
|
|
@ -916,14 +952,27 @@ class CardDavBackend implements BackendInterface, SyncSupport {
|
|||
}
|
||||
} 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.
|
||||
$qb->setMaxResults($limit);
|
||||
$stmt = $qb->executeQuery();
|
||||
$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
$values = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
if (empty($values)) {
|
||||
$result['added'] = [];
|
||||
return $result;
|
||||
}
|
||||
$lastID = $values[array_key_last($values)]['id'];
|
||||
if (count($values) >= $limit) {
|
||||
$result['syncToken'] = 'init_' . $lastID . '_' . $currentToken;
|
||||
$result['result_truncated'] = true;
|
||||
}
|
||||
|
||||
$result['added'] = array_column($values, 'uri');
|
||||
|
||||
$stmt->closeCursor();
|
||||
}
|
||||
return $result;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace OCA\DAV\CardDAV;
|
||||
|
||||
use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException;
|
||||
use OCA\Federation\TrustedServers;
|
||||
use OCP\Accounts\IAccountManager;
|
||||
use OCP\IConfig;
|
||||
|
|
@ -212,14 +211,7 @@ class SystemAddressbook extends AddressBook {
|
|||
}
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ class RootCollection extends SimpleCollection {
|
|||
$userManager,
|
||||
$dispatcher,
|
||||
$contactsSharingBackend,
|
||||
$config
|
||||
);
|
||||
$usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, $pluginManager, $userSession->getUser(), $groupManager, 'principals/users');
|
||||
$usersAddressBookRoot->disableListing = $disableListing;
|
||||
|
|
@ -142,6 +143,7 @@ class RootCollection extends SimpleCollection {
|
|||
$userManager,
|
||||
$dispatcher,
|
||||
$contactsSharingBackend,
|
||||
$config
|
||||
);
|
||||
$systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, $pluginManager, $userSession->getUser(), $groupManager, 'principals/system');
|
||||
$systemAddressBookRoot->disableListing = $disableListing;
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ class CardDavBackendTest extends TestCase {
|
|||
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->groupManager = $this->createMock(IGroupManager::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->principal = $this->getMockBuilder(Principal::class)
|
||||
->setConstructorArgs([
|
||||
$this->userManager,
|
||||
|
|
@ -118,7 +119,7 @@ class CardDavBackendTest extends TestCase {
|
|||
$this->createMock(IAppManager::class),
|
||||
$this->createMock(ProxyMapper::class),
|
||||
$this->createMock(KnownUserService::class),
|
||||
$this->createMock(IConfig::class),
|
||||
$this->config,
|
||||
$this->createMock(IFactory::class)
|
||||
])
|
||||
->setMethods(['getPrincipalByPath', 'getGroupMembership', 'findByUri'])
|
||||
|
|
@ -147,6 +148,7 @@ class CardDavBackendTest extends TestCase {
|
|||
$this->userManager,
|
||||
$this->dispatcher,
|
||||
$this->sharingBackend,
|
||||
$this->config,
|
||||
);
|
||||
// start every test with a empty cards_properties and cards table
|
||||
$query = $this->db->getQueryBuilder();
|
||||
|
|
@ -405,7 +407,7 @@ class CardDavBackendTest extends TestCase {
|
|||
|
||||
public function testDeleteWithoutCard(): void {
|
||||
$this->backend = $this->getMockBuilder(CardDavBackend::class)
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
|
||||
->onlyMethods([
|
||||
'getCardId',
|
||||
'addChange',
|
||||
|
|
@ -513,7 +515,7 @@ class CardDavBackendTest extends TestCase {
|
|||
$cardId = 2;
|
||||
|
||||
$backend = $this->getMockBuilder(CardDavBackend::class)
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend])
|
||||
->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config])
|
||||
->onlyMethods(['getCardId'])->getMock();
|
||||
|
||||
$backend->expects($this->any())->method('getCardId')->willReturn($cardId);
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class SyncServiceTest extends TestCase {
|
|||
'1',
|
||||
'principals/system/system',
|
||||
[]
|
||||
);
|
||||
)['token'];
|
||||
|
||||
$this->assertEquals('http://sabre.io/ns/sync/1', $token);
|
||||
}
|
||||
|
|
@ -175,7 +175,7 @@ END:VCARD';
|
|||
'1',
|
||||
'principals/system/system',
|
||||
[]
|
||||
);
|
||||
)['token'];
|
||||
|
||||
$this->assertEquals('http://sabre.io/ns/sync/2', $token);
|
||||
}
|
||||
|
|
@ -246,7 +246,7 @@ END:VCARD';
|
|||
'1',
|
||||
'principals/system/system',
|
||||
[]
|
||||
);
|
||||
)['token'];
|
||||
|
||||
$this->assertEquals('http://sabre.io/ns/sync/3', $token);
|
||||
}
|
||||
|
|
@ -287,7 +287,7 @@ END:VCARD';
|
|||
'1',
|
||||
'principals/system/system',
|
||||
[]
|
||||
);
|
||||
)['token'];
|
||||
|
||||
$this->assertEquals('http://sabre.io/ns/sync/4', $token);
|
||||
}
|
||||
|
|
@ -430,7 +430,7 @@ END:VCARD';
|
|||
'1',
|
||||
'principals/system/system',
|
||||
[]
|
||||
);
|
||||
)['token'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -472,7 +472,7 @@ END:VCARD';
|
|||
'1',
|
||||
'principals/system/system',
|
||||
[]
|
||||
);
|
||||
)['token'];
|
||||
}
|
||||
|
||||
public function providerUseAbsoluteUriReport(): array {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,12 @@ class SyncFederationAddressBooks {
|
|||
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) {
|
||||
do {
|
||||
$newToken = $this->syncService->syncRemoteAddressBook($url, $cardDavUser, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetBookProperties);
|
||||
} while (str_contains($newToken, 'init_'));
|
||||
}
|
||||
$this->dbHandler->setServerStatus($url, TrustedServers::STATUS_OK, $newToken);
|
||||
} else {
|
||||
$this->logger->debug("Sync Token for $url unchanged from previous sync");
|
||||
|
|
|
|||
Loading…
Reference in a new issue