From b41d19ae5b4340eb11a726eb445c944f57d0190d Mon Sep 17 00:00:00 2001 From: provokateurin Date: Wed, 8 Oct 2025 18:22:39 +0200 Subject: [PATCH 1/2] chore(PreviewMapper): Remove unused and broken deleteAll method Signed-off-by: provokateurin --- lib/private/Preview/Db/PreviewMapper.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/private/Preview/Db/PreviewMapper.php b/lib/private/Preview/Db/PreviewMapper.php index e6ca2e720f358..26e7e3408c452 100644 --- a/lib/private/Preview/Db/PreviewMapper.php +++ b/lib/private/Preview/Db/PreviewMapper.php @@ -168,11 +168,6 @@ public function getLocationId(string $bucket, string $objectStore): int { } } - public function deleteAll(): void { - $delete = $this->db->getQueryBuilder(); - $delete->delete($this->getTableName()); - } - /** * @return \Generator */ From fee65e58fe1de5d91d421e7d1484f3fb142a3d78 Mon Sep 17 00:00:00 2001 From: provokateurin Date: Wed, 8 Oct 2025 18:22:10 +0200 Subject: [PATCH 2/2] feat(preview): Expire previews Signed-off-by: provokateurin --- config/config.sample.php | 8 +++++ core/BackgroundJobs/ExpirePreviewsJob.php | 38 +++++++++++++++++++++ lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/Preview/Db/PreviewMapper.php | 10 +++++- lib/private/Preview/PreviewService.php | 21 ++++++++++-- lib/private/Setup.php | 2 ++ 7 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 core/BackgroundJobs/ExpirePreviewsJob.php diff --git a/config/config.sample.php b/config/config.sample.php index 20409380e49c4..9e453243fec93 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -2775,4 +2775,12 @@ * Defaults to ``true`` */ 'enable_lazy_objects' => true, + + /** + * Delete previews older than a certain number of days to reduce storage usage. + * Less than one day is not allowed, so set it to 0 to disable the deletion. + * + * Defaults to ``0``. + */ + 'preview_expiration_days' => 0, ]; diff --git a/core/BackgroundJobs/ExpirePreviewsJob.php b/core/BackgroundJobs/ExpirePreviewsJob.php new file mode 100644 index 0000000000000..68e2258a52fc5 --- /dev/null +++ b/core/BackgroundJobs/ExpirePreviewsJob.php @@ -0,0 +1,38 @@ +setTimeSensitivity(IJob::TIME_INSENSITIVE); + $this->setInterval(60 * 60 * 24); + } + + protected function run($argument): void { + $days = $this->config->getSystemValueInt('preview_expiration_days'); + if ($days <= 0) { + return; + } + + $this->service->deleteExpiredPreviews($days); + } +} diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index add490d2b0681..323a3d5335d40 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1248,6 +1248,7 @@ 'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => $baseDir . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php', 'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => $baseDir . '/core/BackgroundJobs/CheckForUserCertificates.php', 'OC\\Core\\BackgroundJobs\\CleanupLoginFlowV2' => $baseDir . '/core/BackgroundJobs/CleanupLoginFlowV2.php', + 'OC\\Core\\BackgroundJobs\\ExpirePreviewsJob' => $baseDir . '/core/BackgroundJobs/ExpirePreviewsJob.php', 'OC\\Core\\BackgroundJobs\\GenerateMetadataJob' => $baseDir . '/core/BackgroundJobs/GenerateMetadataJob.php', 'OC\\Core\\BackgroundJobs\\LookupServerSendCheckBackgroundJob' => $baseDir . '/core/BackgroundJobs/LookupServerSendCheckBackgroundJob.php', 'OC\\Core\\BackgroundJobs\\MovePreviewJob' => $baseDir . '/core/BackgroundJobs/MovePreviewJob.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 6c6507f8b22a0..4066ee2ef7a1d 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1289,6 +1289,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\BackgroundJobs\\BackgroundCleanupUpdaterBackupsJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php', 'OC\\Core\\BackgroundJobs\\CheckForUserCertificates' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CheckForUserCertificates.php', 'OC\\Core\\BackgroundJobs\\CleanupLoginFlowV2' => __DIR__ . '/../../..' . '/core/BackgroundJobs/CleanupLoginFlowV2.php', + 'OC\\Core\\BackgroundJobs\\ExpirePreviewsJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/ExpirePreviewsJob.php', 'OC\\Core\\BackgroundJobs\\GenerateMetadataJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/GenerateMetadataJob.php', 'OC\\Core\\BackgroundJobs\\LookupServerSendCheckBackgroundJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/LookupServerSendCheckBackgroundJob.php', 'OC\\Core\\BackgroundJobs\\MovePreviewJob' => __DIR__ . '/../../..' . '/core/BackgroundJobs/MovePreviewJob.php', diff --git a/lib/private/Preview/Db/PreviewMapper.php b/lib/private/Preview/Db/PreviewMapper.php index 26e7e3408c452..ec414cc512c9d 100644 --- a/lib/private/Preview/Db/PreviewMapper.php +++ b/lib/private/Preview/Db/PreviewMapper.php @@ -9,6 +9,8 @@ namespace OC\Preview\Db; +use DateInterval; +use DateTimeImmutable; use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\QBMapper; use OCP\DB\Exception; @@ -25,6 +27,7 @@ class PreviewMapper extends QBMapper { private const TABLE_NAME = 'previews'; private const LOCATION_TABLE_NAME = 'preview_locations'; private const VERSION_TABLE_NAME = 'preview_versions'; + public const MAX_CHUNK_SIZE = 1000; public function __construct( IDBConnection $db, @@ -171,11 +174,16 @@ public function getLocationId(string $bucket, string $objectStore): int { /** * @return \Generator */ - public function getPreviews(int $lastId, int $limit = 1000): \Generator { + public function getPreviews(int $lastId, int $limit = self::MAX_CHUNK_SIZE, ?int $maxAgeDays = null): \Generator { $qb = $this->db->getQueryBuilder(); $this->joinLocation($qb) ->where($qb->expr()->gt('p.id', $qb->createNamedParameter($lastId, IQueryBuilder::PARAM_INT))) ->setMaxResults($limit); + + if ($maxAgeDays !== null) { + $qb->andWhere($qb->expr()->lt('mtime', $qb->createNamedParameter((new DateTimeImmutable())->sub(new DateInterval('P' . $maxAgeDays . 'D'))->getTimestamp(), IQueryBuilder::PARAM_INT))); + } + return $this->yieldEntities($qb); } diff --git a/lib/private/Preview/PreviewService.php b/lib/private/Preview/PreviewService.php index 8d30ae8a40212..31208d2c46e7b 100644 --- a/lib/private/Preview/PreviewService.php +++ b/lib/private/Preview/PreviewService.php @@ -85,7 +85,7 @@ public function getPreviewsForMimeTypes(array $mimeTypes): \Generator { public function deleteAll(): void { $lastId = 0; while (true) { - $previews = $this->previewMapper->getPreviews($lastId, 1000); + $previews = $this->previewMapper->getPreviews($lastId, PreviewMapper::MAX_CHUNK_SIZE); $i = 0; foreach ($previews as $preview) { $this->deletePreview($preview); @@ -93,7 +93,7 @@ public function deleteAll(): void { $lastId = $preview->getId(); } - if ($i !== 1000) { + if ($i !== PreviewMapper::MAX_CHUNK_SIZE) { break; } } @@ -106,4 +106,21 @@ public function deleteAll(): void { public function getAvailablePreviews(array $fileIds): array { return $this->previewMapper->getAvailablePreviews($fileIds); } + + public function deleteExpiredPreviews(int $maxAgeDays): void { + $lastId = 0; + while (true) { + $previews = $this->previewMapper->getPreviews($lastId, PreviewMapper::MAX_CHUNK_SIZE, $maxAgeDays); + $i = 0; + foreach ($previews as $preview) { + $this->deletePreview($preview); + $i++; + $lastId = $preview->getId(); + } + + if ($i !== PreviewMapper::MAX_CHUNK_SIZE) { + break; + } + } + } } diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 8781127b30afc..3d0c557733048 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -14,6 +14,7 @@ use InvalidArgumentException; use OC\Authentication\Token\PublicKeyTokenProvider; use OC\Authentication\Token\TokenCleanupJob; +use OC\Core\BackgroundJobs\ExpirePreviewsJob; use OC\Core\BackgroundJobs\GenerateMetadataJob; use OC\Core\BackgroundJobs\MovePreviewJob; use OC\Log\Rotate; @@ -507,6 +508,7 @@ public static function installBackgroundJobs(): void { $jobList->add(CleanupDeletedUsers::class); $jobList->add(GenerateMetadataJob::class); $jobList->add(MovePreviewJob::class); + $jobList->add(ExpirePreviewsJob::class); } /**