2025-09-16 05:34:41 -04:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH
|
|
|
|
|
* SPDX-FileContributor: Carl Schwan
|
|
|
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
namespace OC\Preview;
|
|
|
|
|
|
|
|
|
|
use OC\Preview\Db\Preview;
|
|
|
|
|
use OC\Preview\Db\PreviewMapper;
|
|
|
|
|
use OC\Preview\Storage\StorageFactory;
|
2025-10-14 04:24:49 -04:00
|
|
|
use OCP\DB\Exception;
|
|
|
|
|
use OCP\Files\NotPermittedException;
|
2025-09-16 05:34:41 -04:00
|
|
|
use OCP\IDBConnection;
|
|
|
|
|
|
|
|
|
|
class PreviewService {
|
|
|
|
|
public function __construct(
|
|
|
|
|
private readonly StorageFactory $storageFactory,
|
|
|
|
|
private readonly PreviewMapper $previewMapper,
|
|
|
|
|
private readonly IDBConnection $connection,
|
|
|
|
|
) {
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-14 04:24:49 -04:00
|
|
|
/**
|
|
|
|
|
* @throws NotPermittedException
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
2025-09-16 05:34:41 -04:00
|
|
|
public function deletePreview(Preview $preview): void {
|
|
|
|
|
$this->storageFactory->deletePreview($preview);
|
|
|
|
|
$this->previewMapper->delete($preview);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get storageId and fileIds for which we have at least one preview.
|
|
|
|
|
*
|
|
|
|
|
* @return \Generator<array{storageId: int, fileIds: int[]}>
|
|
|
|
|
*/
|
|
|
|
|
public function getAvailableFileIds(): \Generator {
|
2025-10-23 04:15:44 -04:00
|
|
|
$lastId = null;
|
|
|
|
|
while (true) {
|
|
|
|
|
$maxQb = $this->connection->getQueryBuilder();
|
|
|
|
|
$maxQb->selectAlias($maxQb->func()->max('id'), 'max_id')
|
|
|
|
|
->from($this->previewMapper->getTableName())
|
|
|
|
|
->groupBy('file_id')
|
|
|
|
|
->orderBy('max_id', 'ASC');
|
|
|
|
|
|
|
|
|
|
$qb = $this->connection->getQueryBuilder();
|
|
|
|
|
|
|
|
|
|
if ($lastId !== null) {
|
|
|
|
|
$maxQb->andWhere($maxQb->expr()->gt('id', $qb->createNamedParameter($lastId)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$qb->select('id', 'file_id', 'storage_id')
|
|
|
|
|
->from($this->previewMapper->getTableName(), 'p1')
|
|
|
|
|
->innerJoin('p1', $qb->createFunction('(' . $maxQb->getSQL() . ')'), 'p2', $qb->expr()->eq('p1.id', 'p2.max_id'))
|
|
|
|
|
->setMaxResults(1000);
|
|
|
|
|
|
|
|
|
|
$result = $qb->executeQuery();
|
|
|
|
|
|
|
|
|
|
$lastStorageId = -1;
|
|
|
|
|
/** @var int[] $fileIds */
|
|
|
|
|
$fileIds = [];
|
|
|
|
|
|
|
|
|
|
$found = false;
|
|
|
|
|
// Previews next to each others in the database are likely in the same storage, so group them
|
|
|
|
|
while ($row = $result->fetch()) {
|
|
|
|
|
$found = true;
|
|
|
|
|
if ($lastStorageId !== (int)$row['storage_id']) {
|
|
|
|
|
if ($lastStorageId !== -1) {
|
|
|
|
|
yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
|
|
|
|
|
$fileIds = [];
|
|
|
|
|
}
|
|
|
|
|
$lastStorageId = (int)$row['storage_id'];
|
2025-09-16 05:34:41 -04:00
|
|
|
}
|
2025-10-23 04:15:44 -04:00
|
|
|
$fileIds[] = (int)$row['file_id'];
|
|
|
|
|
$lastId = $row['id'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (count($fileIds) > 0) {
|
|
|
|
|
yield ['storageId' => $lastStorageId, 'fileIds' => $fileIds];
|
2025-09-16 05:34:41 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-23 04:15:44 -04:00
|
|
|
if (!$found) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-09-16 05:34:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return \Generator<Preview>
|
|
|
|
|
*/
|
2025-09-30 09:56:31 -04:00
|
|
|
public function getAvailablePreviewsForFile(int $fileId): \Generator {
|
|
|
|
|
return $this->previewMapper->getAvailablePreviewsForFile($fileId);
|
2025-09-26 06:26:43 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-09-30 09:56:31 -04:00
|
|
|
* @param string[] $mimeTypes
|
2025-09-26 06:26:43 -04:00
|
|
|
* @return \Generator<Preview>
|
|
|
|
|
*/
|
|
|
|
|
public function getPreviewsForMimeTypes(array $mimeTypes): \Generator {
|
|
|
|
|
return $this->previewMapper->getPreviewsForMimeTypes($mimeTypes);
|
2025-09-16 05:34:41 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-14 04:24:49 -04:00
|
|
|
/**
|
|
|
|
|
* @throws NotPermittedException
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
2025-09-16 05:34:41 -04:00
|
|
|
public function deleteAll(): void {
|
2026-02-09 12:30:42 -05:00
|
|
|
$lastId = '0';
|
2025-09-16 05:34:41 -04:00
|
|
|
while (true) {
|
2025-10-08 12:22:10 -04:00
|
|
|
$previews = $this->previewMapper->getPreviews($lastId, PreviewMapper::MAX_CHUNK_SIZE);
|
2025-09-16 05:34:41 -04:00
|
|
|
$i = 0;
|
2025-10-14 04:24:49 -04:00
|
|
|
|
|
|
|
|
// FIXME: Should we use transaction here? Du to the I/O created when
|
|
|
|
|
// deleting the previews from the storage, which might be on a network
|
|
|
|
|
// This might take a non trivial amount of time where the DB is locked.
|
2025-09-16 05:34:41 -04:00
|
|
|
foreach ($previews as $preview) {
|
|
|
|
|
$this->deletePreview($preview);
|
|
|
|
|
$i++;
|
|
|
|
|
$lastId = $preview->getId();
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-08 12:22:10 -04:00
|
|
|
if ($i !== PreviewMapper::MAX_CHUNK_SIZE) {
|
2025-09-16 05:34:41 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param int[] $fileIds
|
|
|
|
|
* @return array<int, Preview[]>
|
|
|
|
|
*/
|
|
|
|
|
public function getAvailablePreviews(array $fileIds): array {
|
|
|
|
|
return $this->previewMapper->getAvailablePreviews($fileIds);
|
|
|
|
|
}
|
2025-10-08 12:22:10 -04:00
|
|
|
|
|
|
|
|
public function deleteExpiredPreviews(int $maxAgeDays): void {
|
|
|
|
|
$lastId = '0';
|
|
|
|
|
$startTime = time();
|
|
|
|
|
while (true) {
|
|
|
|
|
try {
|
|
|
|
|
$this->connection->beginTransaction();
|
|
|
|
|
|
|
|
|
|
$previews = $this->previewMapper->getPreviews($lastId, PreviewMapper::MAX_CHUNK_SIZE, $maxAgeDays);
|
|
|
|
|
$i = 0;
|
|
|
|
|
foreach ($previews as $preview) {
|
|
|
|
|
$this->deletePreview($preview);
|
|
|
|
|
$i++;
|
|
|
|
|
$lastId = $preview->getId();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->connection->commit();
|
|
|
|
|
|
|
|
|
|
if ($i !== PreviewMapper::MAX_CHUNK_SIZE) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
$this->connection->commit();
|
|
|
|
|
|
|
|
|
|
throw $e;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Stop if execution time is more than one hour.
|
|
|
|
|
if (time() - $startTime > 3600) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-16 05:34:41 -04:00
|
|
|
}
|