Skip to content

Commit d6c95b2

Browse files
committed
A dav plugin to allow pagination of multipart responses
Signed-off-by: Robin Appelman <[email protected]>
1 parent fd9ff58 commit d6c95b2

File tree

13 files changed

+662
-2
lines changed

13 files changed

+662
-2
lines changed

3rdparty

Submodule 3rdparty updated 37 files

apps/dav/appinfo/info.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<name>WebDAV</name>
66
<summary>WebDAV endpoint</summary>
77
<description>WebDAV endpoint</description>
8-
<version>1.10.0</version>
8+
<version>1.10.1</version>
99
<licence>agpl</licence>
1010
<author>owncloud.org</author>
1111
<namespace>DAV</namespace>
@@ -23,6 +23,7 @@
2323
<job>OCA\DAV\BackgroundJob\CleanupDirectLinksJob</job>
2424
<job>OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob</job>
2525
<job>OCA\DAV\BackgroundJob\CleanupInvitationTokenJob</job>
26+
<job>OCA\DAV\BackgroundJob\CleanupPaginateCacheJob</job>
2627
</background-jobs>
2728

2829
<repair-steps>

apps/dav/composer/composer/autoload_classmap.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
'OCA\\DAV\\Avatars\\RootCollection' => $baseDir . '/../lib/Avatars/RootCollection.php',
1414
'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => $baseDir . '/../lib/BackgroundJob/CleanupDirectLinksJob.php',
1515
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => $baseDir . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
16+
'OCA\\DAV\\BackgroundJob\\CleanupPaginateCacheJob' => $baseDir . '/../lib/BackgroundJob/CleanupPaginateCacheJob.php',
1617
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => $baseDir . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
1718
'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => $baseDir . '/../lib/BackgroundJob/RefreshWebcalJob.php',
1819
'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => $baseDir . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php',
@@ -178,6 +179,10 @@
178179
'OCA\\DAV\\Migration\\Version1008Date20181105110300' => $baseDir . '/../lib/Migration/Version1008Date20181105110300.php',
179180
'OCA\\DAV\\Migration\\Version1008Date20181105112049' => $baseDir . '/../lib/Migration/Version1008Date20181105112049.php',
180181
'OCA\\DAV\\Migration\\Version1008Date20181114084440' => $baseDir . '/../lib/Migration/Version1008Date20181114084440.php',
182+
'OCA\\DAV\\Migration\\Version1009Date20181108161232' => $baseDir . '/../lib/Migration/Version1009Date20181108161232.php',
183+
'OCA\\DAV\\Paginate\\LimitedCopyIterator' => $baseDir . '/../lib/Paginate/LimitedCopyIterator.php',
184+
'OCA\\DAV\\Paginate\\PaginateCache' => $baseDir . '/../lib/Paginate/PaginateCache.php',
185+
'OCA\\DAV\\Paginate\\PaginatePlugin' => $baseDir . '/../lib/Paginate/PaginatePlugin.php',
181186
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
182187
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => $baseDir . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
183188
'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php',

apps/dav/composer/composer/autoload_static.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class ComposerStaticInitDAV
2828
'OCA\\DAV\\Avatars\\RootCollection' => __DIR__ . '/..' . '/../lib/Avatars/RootCollection.php',
2929
'OCA\\DAV\\BackgroundJob\\CleanupDirectLinksJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupDirectLinksJob.php',
3030
'OCA\\DAV\\BackgroundJob\\CleanupInvitationTokenJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupInvitationTokenJob.php',
31+
'OCA\\DAV\\BackgroundJob\\CleanupPaginateCacheJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupPaginateCacheJob.php',
3132
'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php',
3233
'OCA\\DAV\\BackgroundJob\\RefreshWebcalJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/RefreshWebcalJob.php',
3334
'OCA\\DAV\\BackgroundJob\\RegisterRegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/BackgroundJob/RegisterRegenerateBirthdayCalendars.php',
@@ -193,6 +194,10 @@ class ComposerStaticInitDAV
193194
'OCA\\DAV\\Migration\\Version1008Date20181105110300' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181105110300.php',
194195
'OCA\\DAV\\Migration\\Version1008Date20181105112049' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181105112049.php',
195196
'OCA\\DAV\\Migration\\Version1008Date20181114084440' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181114084440.php',
197+
'OCA\\DAV\\Migration\\Version1009Date20181108161232' => __DIR__ . '/..' . '/../lib/Migration/Version1009Date20181108161232.php',
198+
'OCA\\DAV\\Paginate\\LimitedCopyIterator' => __DIR__ . '/..' . '/../lib/Paginate/LimitedCopyIterator.php',
199+
'OCA\\DAV\\Paginate\\PaginateCache' => __DIR__ . '/..' . '/../lib/Paginate/PaginateCache.php',
200+
'OCA\\DAV\\Paginate\\PaginatePlugin' => __DIR__ . '/..' . '/../lib/Paginate/PaginatePlugin.php',
196201
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningNode' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningNode.php',
197202
'OCA\\DAV\\Provisioning\\Apple\\AppleProvisioningPlugin' => __DIR__ . '/..' . '/../lib/Provisioning/Apple/AppleProvisioningPlugin.php',
198203
'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php',
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
/**
3+
* @copyright Copyright (c) 2018 Robin Appelman <[email protected]>
4+
*
5+
* @license GNU AGPL version 3 or any later version
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as
9+
* published by the Free Software Foundation, either version 3 of the
10+
* License, or (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*
20+
*/
21+
22+
namespace OCA\DAV\BackgroundJob;
23+
24+
use OC\BackgroundJob\Job;
25+
use OCA\DAV\Paginate\PaginateCache;
26+
27+
class CleanupPaginateCacheJob extends Job {
28+
29+
/** @var PaginateCache */
30+
private $cache;
31+
32+
public function __construct(PaginateCache $cache) {
33+
$this->cache = $cache;
34+
}
35+
36+
public function run($argument) {
37+
$this->cache->cleanup();
38+
}
39+
40+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
namespace OCA\DAV\Migration;
3+
4+
use Doctrine\DBAL\Types\Type;
5+
use OCP\DB\ISchemaWrapper;
6+
use OCP\Migration\SimpleMigrationStep;
7+
use OCP\Migration\IOutput;
8+
9+
class Version1009Date20181108161232 extends SimpleMigrationStep {
10+
public function name(): string {
11+
return 'Add dav_page_cache table';
12+
}
13+
14+
public function description(): string {
15+
return 'Add table to cache webdav multistatus responses for pagination purpose';
16+
}
17+
18+
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
19+
/** @var ISchemaWrapper $schema */
20+
$schema = $schemaClosure();
21+
22+
if (!$schema->hasTable('dav_page_cache')) {
23+
$table = $schema->createTable('dav_page_cache');
24+
25+
$table->addColumn('id', Type::BIGINT, [
26+
'autoincrement' => true
27+
]);
28+
$table->addColumn('url_hash', Type::STRING, [
29+
'notnull' => true,
30+
'length' => 32,
31+
]);
32+
$table->addColumn('token', Type::STRING, [
33+
'notnull' => true,
34+
'length' => 32
35+
]);
36+
$table->addColumn('result_index', Type::INTEGER, [
37+
'notnull' => true
38+
]);
39+
$table->addColumn('result_value', TYPE::TEXT, [
40+
'notnull' => false,
41+
]);
42+
$table->addColumn('insert_time', TYPE::BIGINT, [
43+
'notnull' => false,
44+
]);
45+
46+
$table->setPrimaryKey(['id'], 'dav_page_cache_id_index');
47+
$table->addIndex(['token', 'url_hash'], 'dav_page_cache_token_url');
48+
$table->addUniqueIndex(['token', 'url_hash', 'result_index'], 'dav_page_cache_url_index');
49+
$table->addIndex(['result_index'], 'dav_page_cache_index');
50+
$table->addIndex(['insert_time'], 'dav_page_cache_time');
51+
}
52+
53+
return $schema;
54+
}
55+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
/**
3+
* @copyright Copyright (c) 2018 Robin Appelman <[email protected]>
4+
*
5+
* @license GNU AGPL version 3 or any later version
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as
9+
* published by the Free Software Foundation, either version 3 of the
10+
* License, or (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*
20+
*/
21+
22+
namespace OCA\DAV\Paginate;
23+
24+
/**
25+
* Save a copy of the first X items into a separate iterator
26+
*
27+
* this allows us to pass the iterator to the cache while keeping a copy
28+
* of the first X items
29+
*/
30+
class LimitedCopyIterator extends \AppendIterator {
31+
/** @var array */
32+
private $copy;
33+
34+
public function __construct(\Traversable $iterator, int $count) {
35+
parent::__construct();
36+
37+
if (!$iterator instanceof \Iterator) {
38+
$iterator = new \IteratorIterator($iterator);
39+
}
40+
$iterator = new \NoRewindIterator($iterator);
41+
42+
while($iterator->valid() && count($this->copy) < $count) {
43+
$this->copy[] = $iterator->current();
44+
$iterator->next();
45+
}
46+
47+
$this->append($this->getFirstItems());
48+
$this->append($iterator);
49+
}
50+
51+
public function getFirstItems(): \Iterator {
52+
return new \ArrayIterator($this->copy);
53+
}
54+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
/**
3+
* @copyright Copyright (c) 2018 Robin Appelman <[email protected]>
4+
*
5+
* @license GNU AGPL version 3 or any later version
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as
9+
* published by the Free Software Foundation, either version 3 of the
10+
* License, or (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*
20+
*/
21+
22+
namespace OCA\DAV\Paginate;
23+
24+
25+
use OCP\AppFramework\Utility\ITimeFactory;
26+
use OCP\DB\QueryBuilder\IQueryBuilder;
27+
use OCP\IDBConnection;
28+
use OCP\Security\ISecureRandom;
29+
30+
class PaginateCache {
31+
const TTL = 3600;
32+
33+
/** @var IDBConnection */
34+
private $database;
35+
/** @var ISecureRandom */
36+
private $random;
37+
/** @var ITimeFactory */
38+
private $timeFactory;
39+
40+
public function __construct(
41+
IDBConnection $database,
42+
ISecureRandom $random,
43+
ITimeFactory $timeFactory
44+
) {
45+
$this->database = $database;
46+
$this->random = $random;
47+
$this->timeFactory = $timeFactory;
48+
}
49+
50+
public function store(string $uri, \Iterator $items): array {
51+
$token = $this->random->generate(32);
52+
$now = $this->timeFactory->getTime();
53+
54+
$query = $this->database->getQueryBuilder();
55+
$query->insert('dav_page_cache')
56+
->values([
57+
'url_hash' => $query->createNamedParameter(md5($uri), IQueryBuilder::PARAM_STR),
58+
'token' => $query->createNamedParameter($token, IQueryBuilder::PARAM_STR),
59+
'insert_time' => $query->createNamedParameter($now, IQueryBuilder::PARAM_INT),
60+
'result_index' => $query->createParameter('index'),
61+
'result_value' => $query->createParameter('value'),
62+
]);
63+
64+
$count = 0;
65+
foreach ($items as $item) {
66+
$value = json_encode($item);
67+
$query->setParameter('index', $count, IQueryBuilder::PARAM_INT);
68+
$query->setParameter('value', $value);
69+
$query->execute();
70+
$count++;
71+
}
72+
73+
return [$token, $count];
74+
}
75+
76+
/**
77+
* @param string $url
78+
* @param string $token
79+
* @param int $offset
80+
* @param int $count
81+
* @return array|\Traversable
82+
*/
83+
public function get(string $url, string $token, int $offset, int $count) {
84+
$query = $this->database->getQueryBuilder();
85+
$query->select(['result_value'])
86+
->from('dav_page_cache')
87+
->where($query->expr()->eq('token', $query->createNamedParameter($token)))
88+
->andWhere($query->expr()->eq('url_hash', $query->createNamedParameter(md5($url))))
89+
->andWhere($query->expr()->gte('result_index', $query->createNamedParameter($offset, IQueryBuilder::PARAM_INT)))
90+
->andWhere($query->expr()->lt('result_index', $query->createNamedParameter($offset + $count, IQueryBuilder::PARAM_INT)));
91+
92+
$result = $query->execute();
93+
return array_map(function (string $entry) {
94+
return json_decode($entry, true);
95+
}, $result->fetchAll(\PDO::FETCH_COLUMN));
96+
}
97+
98+
public function cleanup() {
99+
$now = $this->timeFactory->getTime();
100+
101+
$query = $this->database->getQueryBuilder();
102+
$query->delete('dav_page_cache')
103+
->where($query->expr()->lt('insert_time', $query->createNamedParameter($now - self::TTL)));
104+
$query->execute();
105+
}
106+
107+
public function clear() {
108+
$query = $this->database->getQueryBuilder();
109+
$query->delete('dav_page_cache');
110+
$query->execute();
111+
}
112+
}

0 commit comments

Comments
 (0)