Browse Source
feat: emit `preloadCollection` event in DAV
feat: emit `preloadCollection` event in DAV
This allows plugins to preload the content of a Collection to speed-up subsequent per-node PROPFINDs and reduce database load. Signed-off-by: Salvatore Martire <4652631+salmart-dev@users.noreply.github.com>pull/54318/head
10 changed files with 257 additions and 75 deletions
-
1apps/dav/composer/composer/autoload_classmap.php
-
1apps/dav/composer/composer/autoload_static.php
-
4apps/dav/lib/CalDAV/EmbeddedCalDavServer.php
-
46apps/dav/lib/Connector/Sabre/PropFindMonitorPlugin.php
-
55apps/dav/lib/Connector/Sabre/PropFindPreloadNotifyPlugin.php
-
42apps/dav/lib/Connector/Sabre/Server.php
-
2apps/dav/lib/Connector/Sabre/ServerFactory.php
-
3apps/dav/lib/Server.php
-
86apps/dav/tests/unit/Connector/Sabre/PropFindMonitorPluginTest.php
-
92apps/dav/tests/unit/Connector/Sabre/PropFindPreloadNotifyPluginTest.php
@ -0,0 +1,55 @@ |
|||
<?php |
|||
|
|||
declare(strict_types = 1); |
|||
|
|||
/** |
|||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
namespace OCA\DAV\Connector\Sabre; |
|||
|
|||
use Sabre\DAV\ICollection; |
|||
use Sabre\DAV\INode; |
|||
use Sabre\DAV\PropFind; |
|||
use Sabre\DAV\Server; |
|||
use Sabre\DAV\ServerPlugin; |
|||
|
|||
/** |
|||
* This plugin asks other plugins to preload data for a collection, so that |
|||
* subsequent PROPFIND handlers for children do not query the DB on a per-node |
|||
* basis. |
|||
*/ |
|||
class PropFindPreloadNotifyPlugin extends ServerPlugin { |
|||
|
|||
private Server $server; |
|||
|
|||
public function initialize(Server $server): void { |
|||
$this->server = $server; |
|||
$this->server->on('propFind', [$this, 'collectionPreloadNotifier' ], 1); |
|||
} |
|||
|
|||
/** |
|||
* Uses the server instance to emit a `preloadCollection` event to signal |
|||
* to interested plugins that a collection can be preloaded. |
|||
* |
|||
* NOTE: this can be emitted several times, so ideally every plugin |
|||
* should cache what they need and check if a cache exists before |
|||
* re-fetching. |
|||
*/ |
|||
public function collectionPreloadNotifier(PropFind $propFind, INode $node): bool { |
|||
if (!$this->shouldPreload($propFind, $node)) { |
|||
return true; |
|||
} |
|||
|
|||
return $this->server->emit('preloadCollection', [$propFind, $node]); |
|||
} |
|||
|
|||
private function shouldPreload( |
|||
PropFind $propFind, |
|||
INode $node, |
|||
): bool { |
|||
$depth = $propFind->getDepth(); |
|||
return $node instanceof ICollection |
|||
&& ($depth === Server::DEPTH_INFINITY || $depth > 0); |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
<?php |
|||
|
|||
declare(strict_types=1); |
|||
|
|||
/** |
|||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
|||
* SPDX-License-Identifier: AGPL-3.0-or-later |
|||
*/ |
|||
namespace OCA\DAV\Tests\unit\Connector\Sabre; |
|||
|
|||
use OCA\DAV\Connector\Sabre\PropFindPreloadNotifyPlugin; |
|||
use PHPUnit\Framework\Attributes\DataProvider; |
|||
use PHPUnit\Framework\MockObject\MockObject; |
|||
use Sabre\DAV\ICollection; |
|||
use Sabre\DAV\IFile; |
|||
use Sabre\DAV\PropFind; |
|||
use Sabre\DAV\Server; |
|||
use Test\TestCase; |
|||
|
|||
class PropFindPreloadNotifyPluginTest extends TestCase { |
|||
|
|||
private Server&MockObject $server; |
|||
private PropFindPreloadNotifyPlugin $plugin; |
|||
|
|||
protected function setUp(): void { |
|||
parent::setUp(); |
|||
|
|||
$this->server = $this->createMock(Server::class); |
|||
$this->plugin = new PropFindPreloadNotifyPlugin(); |
|||
} |
|||
|
|||
public function testInitialize(): void { |
|||
$this->server |
|||
->expects(self::once()) |
|||
->method('on') |
|||
->with('propFind', |
|||
$this->anything(), 1); |
|||
$this->plugin->initialize($this->server); |
|||
} |
|||
|
|||
public static function dataTestCollectionPreloadNotifier(): array { |
|||
return [ |
|||
'When node is not a collection, should not emit' => [ |
|||
IFile::class, |
|||
1, |
|||
false, |
|||
true |
|||
], |
|||
'When node is a collection but depth is zero, should not emit' => [ |
|||
ICollection::class, |
|||
0, |
|||
false, |
|||
true |
|||
], |
|||
'When node is a collection, and depth > 0, should emit' => [ |
|||
ICollection::class, |
|||
1, |
|||
true, |
|||
true |
|||
], |
|||
'When node is a collection, and depth is infinite, should emit' |
|||
=> [ |
|||
ICollection::class, |
|||
Server::DEPTH_INFINITY, |
|||
true, |
|||
true |
|||
], |
|||
'When called called handler returns false, it should be returned' |
|||
=> [ |
|||
ICollection::class, |
|||
1, |
|||
true, |
|||
false |
|||
] |
|||
]; |
|||
} |
|||
|
|||
#[DataProvider(methodName: 'dataTestCollectionPreloadNotifier')]
|
|||
public function testCollectionPreloadNotifier(string $nodeType, int $depth, bool $shouldEmit, bool $emitReturns): |
|||
void { |
|||
$this->plugin->initialize($this->server); |
|||
$propFind = $this->createMock(PropFind::class); |
|||
$propFind->expects(self::any())->method('getDepth')->willReturn($depth); |
|||
$node = $this->createMock($nodeType); |
|||
|
|||
$expectation = $shouldEmit ? self::once() : self::never(); |
|||
$this->server->expects($expectation)->method('emit')->with('preloadCollection', |
|||
[$propFind, $node])->willReturn($emitReturns); |
|||
$return = $this->plugin->collectionPreloadNotifier($propFind, $node); |
|||
$this->assertEquals($emitReturns, $return); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue