Skip to content

Commit d059d49

Browse files
committed
feat(preview): Expire previews
Signed-off-by: provokateurin <[email protected]>
1 parent 8c06470 commit d059d49

File tree

7 files changed

+95
-3
lines changed

7 files changed

+95
-3
lines changed

config/config.sample.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2917,4 +2917,12 @@
29172917
'fe80::/10',
29182918
'10.0.0.1',
29192919
],
2920+
2921+
/**
2922+
* Delete previews older than a certain number of days to reduce storage usage.
2923+
* Less than one day is not allowed, so set it to 0 to disable the deletion.
2924+
*
2925+
* Defaults to ``0``.
2926+
*/
2927+
'preview_expiration_days' => 0,
29202928
];
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OC\Core\BackgroundJobs;
11+
12+
use OC\Preview\PreviewService;
13+
use OCP\AppFramework\Utility\ITimeFactory;
14+
use OCP\BackgroundJob\IJob;
15+
use OCP\BackgroundJob\TimedJob;
16+
use OCP\IConfig;
17+
18+
class ExpirePreviewsJob extends TimedJob {
19+
public function __construct(
20+
ITimeFactory $time,
21+
private readonly IConfig $config,
22+
private readonly PreviewService $service,
23+
) {
24+
parent::__construct($time);
25+
26+
$this->setTimeSensitivity(IJob::TIME_INSENSITIVE);
27+
$this->setInterval(60 * 60 * 24);
28+
}
29+
30+
protected function run($argument): void {
31+
$days = $this->config->getSystemValueInt('preview_expiration_days');
32+
if ($days <= 0) {
33+
return;
34+
}
35+
36+
$this->service->deleteExpiredPreviews($days);
37+
}
38+
}

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,6 +1305,7 @@
13051305
'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => $baseDir . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php',
13061306
'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => $baseDir . '/core/BackgroundJobs/CheckForUserCertificates.php',
13071307
'OC\\Core\\BackgroundJobs\\CleanupLoginFlowV2' => $baseDir . '/core/BackgroundJobs/CleanupLoginFlowV2.php',
1308+
'OC\\Core\\BackgroundJobs\\ExpirePreviewsJob' => $baseDir . '/core/BackgroundJobs/ExpirePreviewsJob.php',
13081309
'OC\\Core\\BackgroundJobs\\GenerateMetadataJob' => $baseDir . '/core/BackgroundJobs/GenerateMetadataJob.php',
13091310
'OC\\Core\\BackgroundJobs\\LookupServerSendCheckBackgroundJob' => $baseDir . '/core/BackgroundJobs/LookupServerSendCheckBackgroundJob.php',
13101311
'OC\\Core\\BackgroundJobs\\PreviewMigrationJob' => $baseDir . '/core/BackgroundJobs/PreviewMigrationJob.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,6 +1346,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
13461346
'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php',
13471347
'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CheckForUserCertificates.php',
13481348
'OC\\Core\\BackgroundJobs\\CleanupLoginFlowV2' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CleanupLoginFlowV2.php',
1349+
'OC\\Core\\BackgroundJobs\\ExpirePreviewsJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/ExpirePreviewsJob.php',
13491350
'OC\\Core\\BackgroundJobs\\GenerateMetadataJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/GenerateMetadataJob.php',
13501351
'OC\\Core\\BackgroundJobs\\LookupServerSendCheckBackgroundJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/LookupServerSendCheckBackgroundJob.php',
13511352
'OC\\Core\\BackgroundJobs\\PreviewMigrationJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/PreviewMigrationJob.php',

lib/private/Preview/Db/PreviewMapper.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
namespace OC\Preview\Db;
1111

12+
use DateInterval;
13+
use DateTimeImmutable;
1214
use OCP\AppFramework\Db\Entity;
1315
use OCP\AppFramework\Db\QBMapper;
1416
use OCP\DB\Exception;
@@ -26,6 +28,7 @@ class PreviewMapper extends QBMapper {
2628
private const TABLE_NAME = 'previews';
2729
private const LOCATION_TABLE_NAME = 'preview_locations';
2830
private const VERSION_TABLE_NAME = 'preview_versions';
31+
public const MAX_CHUNK_SIZE = 1000;
2932

3033
public function __construct(
3134
IDBConnection $db,
@@ -199,11 +202,16 @@ public function getLocationId(string $bucket, string $objectStore): string {
199202
/**
200203
* @return \Generator<Preview>
201204
*/
202-
public function getPreviews(int $lastId, int $limit = 1000): \Generator {
205+
public function getPreviews(int $lastId, int $limit = self::MAX_CHUNK_SIZE, ?int $maxAgeDays = null): \Generator {
203206
$qb = $this->db->getQueryBuilder();
204207
$this->joinLocation($qb)
205208
->where($qb->expr()->gt('p.id', $qb->createNamedParameter($lastId, IQueryBuilder::PARAM_INT)))
206209
->setMaxResults($limit);
210+
211+
if ($maxAgeDays !== null) {
212+
$qb->andWhere($qb->expr()->lt('mtime', $qb->createNamedParameter((new DateTimeImmutable())->sub(new DateInterval('P' . $maxAgeDays . 'D'))->getTimestamp(), IQueryBuilder::PARAM_INT)));
213+
}
214+
207215
return $this->yieldEntities($qb);
208216

209217
}

lib/private/Preview/PreviewService.php

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public function getPreviewsForMimeTypes(array $mimeTypes): \Generator {
112112
public function deleteAll(): void {
113113
$lastId = 0;
114114
while (true) {
115-
$previews = $this->previewMapper->getPreviews($lastId, 1000);
115+
$previews = $this->previewMapper->getPreviews($lastId, PreviewMapper::MAX_CHUNK_SIZE);
116116
$i = 0;
117117

118118
// FIXME: Should we use transaction here? Du to the I/O created when
@@ -124,7 +124,7 @@ public function deleteAll(): void {
124124
$lastId = $preview->getId();
125125
}
126126

127-
if ($i !== 1000) {
127+
if ($i !== PreviewMapper::MAX_CHUNK_SIZE) {
128128
break;
129129
}
130130
}
@@ -137,4 +137,38 @@ public function deleteAll(): void {
137137
public function getAvailablePreviews(array $fileIds): array {
138138
return $this->previewMapper->getAvailablePreviews($fileIds);
139139
}
140+
141+
public function deleteExpiredPreviews(int $maxAgeDays): void {
142+
143+
$lastId = 0;
144+
$startTime = time();
145+
while (true) {
146+
try {
147+
$this->connection->beginTransaction();
148+
149+
$previews = $this->previewMapper->getPreviews($lastId, PreviewMapper::MAX_CHUNK_SIZE, $maxAgeDays);
150+
$i = 0;
151+
foreach ($previews as $preview) {
152+
$this->deletePreview($preview);
153+
$i++;
154+
$lastId = $preview->getId();
155+
}
156+
157+
$this->connection->commit();
158+
159+
if ($i !== PreviewMapper::MAX_CHUNK_SIZE) {
160+
break;
161+
}
162+
} catch (Exception $e) {
163+
$this->connection->commit();
164+
165+
throw $e;
166+
}
167+
168+
// Stop if execution time is more than one hour.
169+
if (time() - $startTime > 3600) {
170+
return;
171+
}
172+
}
173+
}
140174
}

lib/private/Setup.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use InvalidArgumentException;
1515
use OC\Authentication\Token\PublicKeyTokenProvider;
1616
use OC\Authentication\Token\TokenCleanupJob;
17+
use OC\Core\BackgroundJobs\ExpirePreviewsJob;
1718
use OC\Core\BackgroundJobs\GenerateMetadataJob;
1819
use OC\Core\BackgroundJobs\PreviewMigrationJob;
1920
use OC\Log\Rotate;
@@ -518,6 +519,7 @@ public static function installBackgroundJobs(): void {
518519
$jobList->add(CleanupDeletedUsers::class);
519520
$jobList->add(GenerateMetadataJob::class);
520521
$jobList->add(PreviewMigrationJob::class);
522+
$jobList->add(ExpirePreviewsJob::class);
521523
}
522524

523525
/**

0 commit comments

Comments
 (0)