diff --git a/3rdparty b/3rdparty
index bbef51b59e027..dff719813f5d6 160000
--- a/3rdparty
+++ b/3rdparty
@@ -1 +1 @@
-Subproject commit bbef51b59e027569b9eebf0805342fcb2568848e
+Subproject commit dff719813f5d6d8c31634a7b504063c20a093f13
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml
index 2a11eacd9bece..a0bf84f610af7 100644
--- a/apps/dav/appinfo/info.xml
+++ b/apps/dav/appinfo/info.xml
@@ -5,7 +5,7 @@
WebDAV
WebDAV endpoint
WebDAV endpoint
- 1.10.0
+ 1.10.1
agpl
owncloud.org
DAV
@@ -23,6 +23,7 @@
OCA\DAV\BackgroundJob\CleanupDirectLinksJob
OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob
OCA\DAV\BackgroundJob\CleanupInvitationTokenJob
+ OCA\DAV\BackgroundJob\CleanupPaginateCacheJob
diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php
index 6452cd298d4b8..9967d615166ea 100644
--- a/apps/dav/composer/composer/autoload_classmap.php
+++ b/apps/dav/composer/composer/autoload_classmap.php
@@ -13,6 +13,7 @@
'OCA\\DAV\\Avatars\\RootCollection' => $baseDir . '/../lib/Avatars/RootCollection.php',
'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => $baseDir . '/../lib/BackgroundJob/CleanupDirectLinksJob.php',
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => $baseDir . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
+ 'OCA\\DAV\\BackgroundJob\\CleanupPaginateCacheJob' => $baseDir . '/../lib/BackgroundJob/CleanupPaginateCacheJob.php',
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => $baseDir . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => $baseDir . '/../lib/BackgroundJob/RefreshWebcalJob.php',
'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => $baseDir . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php',
@@ -178,6 +179,10 @@
'OCA\\DAV\\Migration\\Version1008Date20181105110300' => $baseDir . '/../lib/Migration/Version1008Date20181105110300.php',
'OCA\\DAV\\Migration\\Version1008Date20181105112049' => $baseDir . '/../lib/Migration/Version1008Date20181105112049.php',
'OCA\\DAV\\Migration\\Version1008Date20181114084440' => $baseDir . '/../lib/Migration/Version1008Date20181114084440.php',
+ 'OCA\\DAV\\Migration\\Version1009Date20181108161232' => $baseDir . '/../lib/Migration/Version1009Date20181108161232.php',
+ 'OCA\\DAV\\Paginate\\LimitedCopyIterator' => $baseDir . '/../lib/Paginate/LimitedCopyIterator.php',
+ 'OCA\\DAV\\Paginate\\PaginateCache' => $baseDir . '/../lib/Paginate/PaginateCache.php',
+ 'OCA\\DAV\\Paginate\\PaginatePlugin' => $baseDir . '/../lib/Paginate/PaginatePlugin.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php',
diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php
index 19c74d3be0752..cdf651be29f1c 100644
--- a/apps/dav/composer/composer/autoload_static.php
+++ b/apps/dav/composer/composer/autoload_static.php
@@ -28,6 +28,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Avatars\\RootCollection' => __DIR__ . '/..' . '/../lib/Avatars/RootCollection.php',
'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupDirectLinksJob.php',
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
+ 'OCA\\DAV\\BackgroundJob\\CleanupPaginateCacheJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupPaginateCacheJob.php',
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/RefreshWebcalJob.php',
'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php',
@@ -193,6 +194,10 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Migration\\Version1008Date20181105110300' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181105110300.php',
'OCA\\DAV\\Migration\\Version1008Date20181105112049' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181105112049.php',
'OCA\\DAV\\Migration\\Version1008Date20181114084440' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181114084440.php',
+ 'OCA\\DAV\\Migration\\Version1009Date20181108161232' => __DIR__ . '/..' . '/../lib/Migration/Version1009Date20181108161232.php',
+ 'OCA\\DAV\\Paginate\\LimitedCopyIterator' => __DIR__ . '/..' . '/../lib/Paginate/LimitedCopyIterator.php',
+ 'OCA\\DAV\\Paginate\\PaginateCache' => __DIR__ . '/..' . '/../lib/Paginate/PaginateCache.php',
+ 'OCA\\DAV\\Paginate\\PaginatePlugin' => __DIR__ . '/..' . '/../lib/Paginate/PaginatePlugin.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php',
diff --git a/apps/dav/lib/BackgroundJob/CleanupPaginateCacheJob.php b/apps/dav/lib/BackgroundJob/CleanupPaginateCacheJob.php
new file mode 100644
index 0000000000000..aa7de37a4741c
--- /dev/null
+++ b/apps/dav/lib/BackgroundJob/CleanupPaginateCacheJob.php
@@ -0,0 +1,40 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\DAV\BackgroundJob;
+
+use OC\BackgroundJob\Job;
+use OCA\DAV\Paginate\PaginateCache;
+
+class CleanupPaginateCacheJob extends Job {
+
+ /** @var PaginateCache */
+ private $cache;
+
+ public function __construct(PaginateCache $cache) {
+ $this->cache = $cache;
+ }
+
+ public function run($argument) {
+ $this->cache->cleanup();
+ }
+
+}
\ No newline at end of file
diff --git a/apps/dav/lib/Migration/Version1009Date20181108161232.php b/apps/dav/lib/Migration/Version1009Date20181108161232.php
new file mode 100644
index 0000000000000..6aa805a1750a2
--- /dev/null
+++ b/apps/dav/lib/Migration/Version1009Date20181108161232.php
@@ -0,0 +1,55 @@
+hasTable('dav_page_cache')) {
+ $table = $schema->createTable('dav_page_cache');
+
+ $table->addColumn('id', Type::BIGINT, [
+ 'autoincrement' => true
+ ]);
+ $table->addColumn('url_hash', Type::STRING, [
+ 'notnull' => true,
+ 'length' => 32,
+ ]);
+ $table->addColumn('token', Type::STRING, [
+ 'notnull' => true,
+ 'length' => 32
+ ]);
+ $table->addColumn('result_index', Type::INTEGER, [
+ 'notnull' => true
+ ]);
+ $table->addColumn('result_value', TYPE::TEXT, [
+ 'notnull' => false,
+ ]);
+ $table->addColumn('insert_time', TYPE::BIGINT, [
+ 'notnull' => false,
+ ]);
+
+ $table->setPrimaryKey(['id'], 'dav_page_cache_id_index');
+ $table->addIndex(['token', 'url_hash'], 'dav_page_cache_token_url');
+ $table->addUniqueIndex(['token', 'url_hash', 'result_index'], 'dav_page_cache_url_index');
+ $table->addIndex(['result_index'], 'dav_page_cache_index');
+ $table->addIndex(['insert_time'], 'dav_page_cache_time');
+ }
+
+ return $schema;
+ }
+}
diff --git a/apps/dav/lib/Paginate/LimitedCopyIterator.php b/apps/dav/lib/Paginate/LimitedCopyIterator.php
new file mode 100644
index 0000000000000..2b3d06940238e
--- /dev/null
+++ b/apps/dav/lib/Paginate/LimitedCopyIterator.php
@@ -0,0 +1,54 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\DAV\Paginate;
+
+/**
+ * Save a copy of the first X items into a separate iterator
+ *
+ * this allows us to pass the iterator to the cache while keeping a copy
+ * of the first X items
+ */
+class LimitedCopyIterator extends \AppendIterator {
+ /** @var array */
+ private $copy;
+
+ public function __construct(\Traversable $iterator, int $count) {
+ parent::__construct();
+
+ if (!$iterator instanceof \Iterator) {
+ $iterator = new \IteratorIterator($iterator);
+ }
+ $iterator = new \NoRewindIterator($iterator);
+
+ while($iterator->valid() && count($this->copy) < $count) {
+ $this->copy[] = $iterator->current();
+ $iterator->next();
+ }
+
+ $this->append($this->getFirstItems());
+ $this->append($iterator);
+ }
+
+ public function getFirstItems(): \Iterator {
+ return new \ArrayIterator($this->copy);
+ }
+}
\ No newline at end of file
diff --git a/apps/dav/lib/Paginate/PaginateCache.php b/apps/dav/lib/Paginate/PaginateCache.php
new file mode 100644
index 0000000000000..45bff55764ca9
--- /dev/null
+++ b/apps/dav/lib/Paginate/PaginateCache.php
@@ -0,0 +1,112 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\DAV\Paginate;
+
+
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+use OCP\Security\ISecureRandom;
+
+class PaginateCache {
+ const TTL = 3600;
+
+ /** @var IDBConnection */
+ private $database;
+ /** @var ISecureRandom */
+ private $random;
+ /** @var ITimeFactory */
+ private $timeFactory;
+
+ public function __construct(
+ IDBConnection $database,
+ ISecureRandom $random,
+ ITimeFactory $timeFactory
+ ) {
+ $this->database = $database;
+ $this->random = $random;
+ $this->timeFactory = $timeFactory;
+ }
+
+ public function store(string $uri, \Iterator $items): array {
+ $token = $this->random->generate(32);
+ $now = $this->timeFactory->getTime();
+
+ $query = $this->database->getQueryBuilder();
+ $query->insert('dav_page_cache')
+ ->values([
+ 'url_hash' => $query->createNamedParameter(md5($uri), IQueryBuilder::PARAM_STR),
+ 'token' => $query->createNamedParameter($token, IQueryBuilder::PARAM_STR),
+ 'insert_time' => $query->createNamedParameter($now, IQueryBuilder::PARAM_INT),
+ 'result_index' => $query->createParameter('index'),
+ 'result_value' => $query->createParameter('value'),
+ ]);
+
+ $count = 0;
+ foreach ($items as $item) {
+ $value = json_encode($item);
+ $query->setParameter('index', $count, IQueryBuilder::PARAM_INT);
+ $query->setParameter('value', $value);
+ $query->execute();
+ $count++;
+ }
+
+ return [$token, $count];
+ }
+
+ /**
+ * @param string $url
+ * @param string $token
+ * @param int $offset
+ * @param int $count
+ * @return array|\Traversable
+ */
+ public function get(string $url, string $token, int $offset, int $count) {
+ $query = $this->database->getQueryBuilder();
+ $query->select(['result_value'])
+ ->from('dav_page_cache')
+ ->where($query->expr()->eq('token', $query->createNamedParameter($token)))
+ ->andWhere($query->expr()->eq('url_hash', $query->createNamedParameter(md5($url))))
+ ->andWhere($query->expr()->gte('result_index', $query->createNamedParameter($offset, IQueryBuilder::PARAM_INT)))
+ ->andWhere($query->expr()->lt('result_index', $query->createNamedParameter($offset + $count, IQueryBuilder::PARAM_INT)));
+
+ $result = $query->execute();
+ return array_map(function (string $entry) {
+ return json_decode($entry, true);
+ }, $result->fetchAll(\PDO::FETCH_COLUMN));
+ }
+
+ public function cleanup() {
+ $now = $this->timeFactory->getTime();
+
+ $query = $this->database->getQueryBuilder();
+ $query->delete('dav_page_cache')
+ ->where($query->expr()->lt('insert_time', $query->createNamedParameter($now - self::TTL)));
+ $query->execute();
+ }
+
+ public function clear() {
+ $query = $this->database->getQueryBuilder();
+ $query->delete('dav_page_cache');
+ $query->execute();
+ }
+}
\ No newline at end of file
diff --git a/apps/dav/lib/Paginate/PaginatePlugin.php b/apps/dav/lib/Paginate/PaginatePlugin.php
new file mode 100644
index 0000000000000..849d688d4aff5
--- /dev/null
+++ b/apps/dav/lib/Paginate/PaginatePlugin.php
@@ -0,0 +1,110 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\DAV\Paginate;
+
+use Sabre\DAV\Server;
+use Sabre\DAV\ServerPlugin;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+
+class PaginatePlugin extends ServerPlugin {
+ const PAGINATE_HEADER = 'x-nc-paginate';
+ const PAGINATE_TOTAL_HEADER = 'x-nc-paginate-total';
+ const PAGINATE_TOKEN_HEADER = 'x-nc-paginate-token';
+ const PAGINATE_OFFSET_HEADER = 'x-nc-paginate-offset';
+ const PAGINATE_COUNT_HEADER = 'x-nc-paginate-count';
+
+ /** @var int */
+ private $pageSize;
+
+ /** @var Server */
+ private $server;
+
+ /** @var PaginateCache */
+ private $cache;
+
+ public function __construct(PaginateCache $cache, int $pageSize = 100) {
+ $this->cache = $cache;
+ $this->pageSize = $pageSize;
+ }
+
+ function initialize(Server $server) {
+ $this->server = $server;
+ $server->on('beforeMultiStatus', [$this, 'onMultiStatus']);
+ $server->on('method:SEARCH', [$this, 'onMethod'], 1);
+ $server->on('method:PROPFIND', [$this, 'onMethod'], 1);
+ $server->on('method:REPORT', [$this, 'onMethod'], 1);
+ }
+
+ public function getFeatures() {
+ return ['nc-paginate'];
+ }
+
+ function onMultiStatus(&$fileProperties) {
+ $request = $this->server->httpRequest;
+ if (is_array($fileProperties)) {
+ $fileProperties = new \ArrayIterator($fileProperties);
+ }
+ if (
+ $request->hasHeader(self::PAGINATE_HEADER) &&
+ !$request->hasHeader(self::PAGINATE_TOKEN_HEADER)
+ ) {
+ $url = $request->getUrl();
+
+ $pageSize = (int)$request->getHeader(self::PAGINATE_COUNT_HEADER) ?: $this->pageSize;
+ $copyIterator = new LimitedCopyIterator($fileProperties, $pageSize);
+ list($token, $count) = $this->cache->store($url, $copyIterator);
+
+ $fileProperties = $copyIterator->getFirstItems();
+ $this->server->httpResponse->addHeader(self::PAGINATE_HEADER, 'true');
+ $this->server->httpResponse->addHeader(self::PAGINATE_TOKEN_HEADER, $token);
+ $this->server->httpResponse->addHeader(self::PAGINATE_TOTAL_HEADER, $count);
+ }
+ }
+
+ function onMethod(RequestInterface $request, ResponseInterface $response) {
+ if (
+ $request->hasHeader(self::PAGINATE_TOKEN_HEADER) &&
+ $request->hasHeader(self::PAGINATE_OFFSET_HEADER)
+ ) {
+ $url = $this->server->httpRequest->getUrl();
+ $token = $request->getHeader(self::PAGINATE_TOKEN_HEADER);
+ $offset = (int)$request->getHeader(self::PAGINATE_OFFSET_HEADER);
+ $count = (int)$request->getHeader(self::PAGINATE_COUNT_HEADER) ?: $this->pageSize;
+
+ $items = $this->cache->get($url, $token, $offset, $count);
+
+ $response->setStatus(207);
+ $response->addHeader(self::PAGINATE_HEADER, 'true');
+ $response->setHeader('Content-Type', 'application/xml; charset=utf-8');
+ $response->setHeader('Vary', 'Brief,Prefer');
+
+ $prefer = $this->server->getHTTPPrefer();
+ $minimal = $prefer['return'] === 'minimal';
+
+ $data = $this->server->generateMultiStatus($items, $minimal);
+ $response->setBody($data);
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index 7eb68ce58748e..2bf0f7983c736 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -56,6 +56,7 @@
use OCA\DAV\Files\BrowserErrorPagePlugin;
use OCA\DAV\Connector\Sabre\AnonymousOptionsPlugin;
use OCA\DAV\Files\LazySearchBackend;
+use OCA\DAV\Paginate\PaginatePlugin;
use OCA\DAV\Provisioning\Apple\AppleProvisioningPlugin;
use OCA\DAV\SystemTag\SystemTagPlugin;
use OCA\DAV\Upload\ChunkingPlugin;
@@ -192,6 +193,7 @@ public function __construct(IRequest $request, $baseUri) {
$this->server->addPlugin(new CopyEtagHeaderPlugin());
$this->server->addPlugin(new ChunkingPlugin());
+ $this->server->addPlugin(\OC::$server->query(PaginatePlugin::class));
// allow setup of additional plugins
$dispatcher->dispatch('OCA\DAV\Connector\Sabre::addPlugin', $event);
diff --git a/apps/dav/tests/unit/Paginate/LimitedCopyIteratorTest.php b/apps/dav/tests/unit/Paginate/LimitedCopyIteratorTest.php
new file mode 100644
index 0000000000000..c13c84815af6a
--- /dev/null
+++ b/apps/dav/tests/unit/Paginate/LimitedCopyIteratorTest.php
@@ -0,0 +1,38 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\DAV\Tests\Paginate;
+
+use OCA\DAV\Paginate\LimitedCopyIterator;
+use Test\TestCase;
+
+class LimitedCopyIteratorTest extends TestCase {
+ public function testBasic() {
+ $data = range(0, 100);
+
+ $iterator = new LimitedCopyIterator(new \ArrayIterator($data), 10);
+
+ $this->assertEquals(array_slice($data, 0, 10), iterator_to_array($iterator->getFirstItems()));
+ $results = iterator_to_array($iterator);
+ $this->assertEquals($data, $results);
+ $this->assertEquals(array_slice($data, 0, 10), iterator_to_array($iterator->getFirstItems()));
+ }
+}
\ No newline at end of file
diff --git a/apps/dav/tests/unit/Paginate/PaginateCacheTest.php b/apps/dav/tests/unit/Paginate/PaginateCacheTest.php
new file mode 100644
index 0000000000000..eb7d438a32cf4
--- /dev/null
+++ b/apps/dav/tests/unit/Paginate/PaginateCacheTest.php
@@ -0,0 +1,121 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\DAV\Tests\Paginate;
+
+use OCA\DAV\Paginate\PaginateCache;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Security\ISecureRandom;
+use Test\TestCase;
+
+/**
+ * @group DB
+ */
+class PaginateCacheTest extends TestCase {
+ /** @var PaginateCache */
+ private $cache;
+
+ /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */
+ private $timeFactory;
+
+ /** @var ISecureRandom|\PHPUnit_Framework_MockObject_MockObject */
+ private $random;
+
+ const SAMPLE_DATA = [
+ ['foo'],
+ ['bar'],
+ ['asd']
+ ];
+
+ const URL = 'http://example.com';
+
+ private $now = 1000;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $count = 0;
+
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->timeFactory->expects($this->any())
+ ->method('getTime')
+ ->willReturnCallback(function() {
+ return $this->now;
+ });
+ $this->random = $this->createMock(ISecureRandom::class);
+ $this->random->expects($this->any())
+ ->method('generate')
+ ->willReturnCallback(function () use (&$count) {
+ $count++;
+ return 'random_value_' . $count;
+ });
+ $this->cache = new PaginateCache(
+ \OC::$server->getDatabaseConnection(),
+ $this->random,
+ $this->timeFactory
+ );
+ }
+
+ protected function tearDown() {
+ $this->cache->clear();
+
+ return parent::tearDown();
+ }
+
+ public function testBasic() {
+ list($token, $count) = $this->cache->store(self::URL, new \ArrayIterator(self::SAMPLE_DATA));
+ $this->assertEquals(count(self::SAMPLE_DATA), $count);
+
+ $this->assertEquals(self::SAMPLE_DATA, $this->cache->get(self::URL, $token, 0, 5));
+
+ $this->assertEquals(array_slice(self::SAMPLE_DATA, 1, 1), $this->cache->get(self::URL, $token, 1, 1));
+ }
+
+ public function testWrongUrl() {
+ list($token) = $this->cache->store(self::URL, new \ArrayIterator(self::SAMPLE_DATA));
+
+ $this->assertCount(0, $this->cache->get('http://other.url', $token, 0, 5));
+ }
+
+ public function testWrongToken() {
+ $this->cache->store(self::URL, new \ArrayIterator(self::SAMPLE_DATA));
+
+ $this->assertCount(0, $this->cache->get(self::URL, 'wrong_token', 0, 5));
+ }
+
+ public function testCleanup() {
+ $this->now = 1000;
+ list($token1) = $this->cache->store(self::URL, new \ArrayIterator(self::SAMPLE_DATA));
+
+ $this->now = 2000;
+ list($token2) = $this->cache->store(self::URL, new \ArrayIterator(self::SAMPLE_DATA));
+
+ $this->assertCount(count(self::SAMPLE_DATA), $this->cache->get(self::URL, $token1, 0, 5));
+ $this->assertCount(count(self::SAMPLE_DATA), $this->cache->get(self::URL, $token2, 0, 5));
+
+ $this->now = 1500 + PaginateCache::TTL;
+
+ $this->cache->cleanup();
+
+ $this->assertCount(0, $this->cache->get(self::URL, $token1, 0, 5));
+ $this->assertCount(count(self::SAMPLE_DATA), $this->cache->get(self::URL, $token2, 0, 5));
+ }
+}
\ No newline at end of file
diff --git a/apps/dav/tests/unit/Paginate/PaginatePluginTest.php b/apps/dav/tests/unit/Paginate/PaginatePluginTest.php
new file mode 100644
index 0000000000000..bd2a0363533c2
--- /dev/null
+++ b/apps/dav/tests/unit/Paginate/PaginatePluginTest.php
@@ -0,0 +1,117 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\DAV\Tests\Paginate;
+
+use OCA\DAV\Paginate\PaginateCache;
+use OCA\DAV\Paginate\PaginatePlugin;
+use Sabre\DAV\Server;
+use Sabre\HTTP\Request;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\Response;
+use Test\TestCase;
+
+class PaginatePluginTest extends TestCase {
+ /** @var PaginateCache|\PHPUnit_Framework_MockObject_MockObject */
+ private $cache;
+ /** @var Server|\PHPUnit_Framework_MockObject_MockObject */
+ private $server;
+
+ /** @var PaginatePlugin */
+ private $plugin;
+
+ const DATA = [
+ [
+ 'href' => 'foo',
+ '200' => [
+ 'd:getcontentlength' => 10
+ ],
+ ],
+ [
+ 'href' => 'bar',
+ '200' => [
+ 'd:getcontentlength' => 11
+ ],
+ ],
+ [
+ 'href' => 'asd',
+ '200' => [
+ 'd:getcontentlength' => 11
+ ],
+ ]
+ ];
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->cache = $this->createMock(PaginateCache::class);
+ $this->server = $this->createMock(Server::class);
+ $this->server->httpRequest = new Request('GET', 'http://example.com');
+ $this->server->httpResponse = new Response();
+ $this->plugin = new PaginatePlugin($this->cache, 2);
+ $this->plugin->initialize($this->server);
+ }
+
+ public function testPaginateRequest() {
+ $fileProperties = new \ArrayIterator(self::DATA);
+ $storedProps = [];
+ $this->cache->expects($this->any())
+ ->method('store')
+ ->willReturnCallback(function ($url, $props) use (&$storedProps) {
+ $storedProps = $props;
+ return ['token', iterator_count($storedProps)];
+ });
+
+ $this->server->httpRequest->setHeader(PaginatePlugin::PAGINATE_HEADER, 'true');
+
+ $this->plugin->onMultiStatus($fileProperties);
+ $this->assertTrue($this->server->httpResponse->hasHeader(PaginatePlugin::PAGINATE_HEADER));
+ $this->assertTrue($this->server->httpResponse->hasHeader(PaginatePlugin::PAGINATE_TOKEN_HEADER));
+ $this->assertEquals(3, $this->server->httpResponse->getHeader(PaginatePlugin::PAGINATE_TOTAL_HEADER));
+
+ $data = self::DATA;
+ $this->assertEquals(array_splice($data, 0, 2), array_values(iterator_to_array($fileProperties)));
+ $this->assertEquals(self::DATA, iterator_to_array($storedProps));
+ }
+
+ public function testGetCached() {
+ $this->cache->expects($this->any())
+ ->method('get')
+ ->willReturnCallback(function ($url, $token, $offset, $count) {
+ return new \LimitIterator(new \ArrayIterator(self::DATA), $offset, $count);
+ });
+ $responseItems = null;
+ $this->server->expects($this->any())
+ ->method('generateMultiStatus')
+ ->willReturnCallback(function ($items) use (&$responseItems) {
+ $responseItems = $items;
+ });
+
+ $this->server->httpRequest->setHeader(PaginatePlugin::PAGINATE_TOKEN_HEADER, 'foo');
+ $this->server->httpRequest->setHeader(PaginatePlugin::PAGINATE_OFFSET_HEADER, '1');
+ $this->server->httpRequest->setHeader(PaginatePlugin::PAGINATE_COUNT_HEADER, '1');
+
+ $this->plugin->onMethod($this->server->httpRequest, $this->server->httpResponse);
+
+ $this->assertTrue($this->server->httpResponse->hasHeader(PaginatePlugin::PAGINATE_HEADER));
+ $this->assertEquals(array_slice(self::DATA, 1, 1), array_values(iterator_to_array($responseItems)));
+ }
+}
\ No newline at end of file