-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
feat(preview): Expire previews #55632
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /* | ||
| * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors | ||
| * SPDX-License-Identifier: AGPL-3.0-or-later | ||
| */ | ||
|
|
||
| namespace OC\Core\BackgroundJobs; | ||
|
|
||
| use OC\Preview\PreviewService; | ||
| use OCP\AppFramework\Utility\ITimeFactory; | ||
| use OCP\BackgroundJob\IJob; | ||
| use OCP\BackgroundJob\TimedJob; | ||
| use OCP\IConfig; | ||
|
|
||
| class ExpirePreviewsJob extends TimedJob { | ||
| public function __construct( | ||
| ITimeFactory $time, | ||
| private readonly IConfig $config, | ||
| private readonly PreviewService $service, | ||
| ) { | ||
| parent::__construct($time); | ||
|
|
||
| $this->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); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -85,15 +85,15 @@ 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); | ||
| $i++; | ||
| $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) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might make sense to wrap this in a transation and delete by chunk to speed up a bit the process
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, I just copied from the deleteAll method which doesn't have that (probably because it will only be used by tests?)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it's currently only used in tests
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm but with a transaction the DB and storage could get out of sync and I don't know how that is handled. What happens if the files are deleted, but the entries are still in the DB? (Ok they can get out of sync without a transaction as well, but it would be limited to a single preview and not all expired ones)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't call rollback but just commit if something fails in the middle while removing a preview from the storage? |
||
| $this->deletePreview($preview); | ||
| $i++; | ||
| $lastId = $preview->getId(); | ||
| } | ||
|
|
||
| if ($i !== PreviewMapper::MAX_CHUNK_SIZE) { | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do everything at once? If an admin suddenly sets
preview_expiration_daysto a very low value, there will need a lot of work to delete everything, so the cron might timeout at some point.The fact that the background job runs regularly should be enough to lower the number of previews little by little. And we can also add a command to cleanup everything that won't be limited if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but if it is limited to some value per run, then the job might not be able to catch up with deleting all expired previews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sensitive defaults should be acceptable. Maybe increase the job frequency to make sure this works?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A common pattern we have elsewhere is to limit by time. e.g. max 1 hour running