From a84fb39c075b619f6ca90bd085ebe0dc5f907daa Mon Sep 17 00:00:00 2001 From: lastlink Date: Sun, 5 Jan 2025 19:36:15 -0500 Subject: [PATCH] v3 follow up --- lib/Command/Generate.php | 64 +++++++++++++++++-- lib/Command/PreGenerate.php | 48 ++++++++------ .../Version020200Date20190608205303.php | 53 +++++++++++++++ 3 files changed, 140 insertions(+), 25 deletions(-) create mode 100644 lib/Migration/Version020200Date20190608205303.php diff --git a/lib/Command/Generate.php b/lib/Command/Generate.php index 735ceb9..aef2e81 100644 --- a/lib/Command/Generate.php +++ b/lib/Command/Generate.php @@ -38,6 +38,7 @@ use OCP\Files\StorageInvalidException; use OCP\Files\StorageNotAvailableException; use OCP\IConfig; +use OCP\IDBConnection; use OCP\IPreview; use OCP\IUser; use OCP\IUserManager; @@ -53,12 +54,27 @@ class Generate extends Command { /* @return array{width: int, height: int, crop: bool} */ protected array $specifications; + /** @var ?GlobalStoragesService */ protected ?GlobalStoragesService $globalService; + /** @var IUserManager */ protected IUserManager $userManager; + + /** @var IRootFolder */ protected IRootFolder $rootFolder; + + /** @var IPreview */ protected IPreview $previewGenerator; + + /** @var IConfig */ protected IConfig $config; + + /** @var IDBConnection */ + protected $connection; + + /** @var OutputInterface */ protected OutputInterface $output; + + /** @var IManager */ protected IManager $encryptionManager; protected SizeHelper $sizeHelper; @@ -66,6 +82,7 @@ public function __construct(IRootFolder $rootFolder, IUserManager $userManager, IPreview $previewGenerator, IConfig $config, + IDBConnection $connection, IManager $encryptionManager, ContainerInterface $container, SizeHelper $sizeHelper) { @@ -75,6 +92,7 @@ public function __construct(IRootFolder $rootFolder, $this->rootFolder = $rootFolder; $this->previewGenerator = $previewGenerator; $this->config = $config; + $this->connection = $connection; $this->encryptionManager = $encryptionManager; $this->sizeHelper = $sizeHelper; @@ -176,7 +194,7 @@ private function generatePathPreviews(IUser $user, string $path): void { } $pathFolder = $userFolder->get($relativePath); $noPreviewMountPaths = $this->getNoPreviewMountPaths($user); - $this->parseFolder($pathFolder, $noPreviewMountPaths); + $this->parseFolder($pathFolder, $noPreviewMountPaths, $user); } private function generateUserPreviews(IUser $user): void { @@ -185,10 +203,10 @@ private function generateUserPreviews(IUser $user): void { $userFolder = $this->rootFolder->getUserFolder($user->getUID()); $noPreviewMountPaths = $this->getNoPreviewMountPaths($user); - $this->parseFolder($userFolder, $noPreviewMountPaths); + $this->parseFolder($userFolder, $noPreviewMountPaths, $user); } - private function parseFolder(Folder $folder, array $noPreviewMountPaths): void { + private function parseFolder(Folder $folder, array $noPreviewMountPaths, IUser $user): void { try { $folderPath = $folder->getPath(); @@ -206,8 +224,44 @@ private function parseFolder(Folder $folder, array $noPreviewMountPaths): void { foreach ($nodes as $node) { if ($node instanceof Folder) { $this->parseFolder($node, $noPreviewMountPaths); - } elseif ($node instanceof File) { - $this->parseFile($node); + } else if ($node instanceof File) { + $is_locked = false; + $qb = $this->connection->getQueryBuilder(); + $row = $qb->select('*') + ->from('preview_generation') + ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($node->getId()))) + ->setMaxResults(1) + ->execute() + ->fetch(); + if ($row !== false) { + if ($row['locked'] == 1) { + // already being processed + $is_locked = true; + } else { + $qb->update('preview_generation') + ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($node->getId()))) + ->set('locked', $qb->createNamedParameter(true)) + ->execute(); + } + } else { + $qb->insert('preview_generation') + ->values([ + 'uid' => $qb->createNamedParameter($user->getUID()), + 'file_id' => $qb->createNamedParameter($node->getId()), + 'locked' => $qb->createNamedParameter(true), + ]) + ->execute(); + } + + if ($is_locked === false) { + try { + $this->parseFile($node); + } finally { + $qb->delete('preview_generation') + ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($node->getId()))) + ->execute(); + } + } } } } catch (StorageNotAvailableException|StorageInvalidException $e) { diff --git a/lib/Command/PreGenerate.php b/lib/Command/PreGenerate.php index b7d0380..e42fa79 100644 --- a/lib/Command/PreGenerate.php +++ b/lib/Command/PreGenerate.php @@ -56,8 +56,6 @@ class PreGenerate extends Command { protected OutputInterface $output; protected IManager $encryptionManager; protected ITimeFactory $time; - protected NoMediaService $noMediaService; - protected SizeHelper $sizeHelper; /** * @param string $appName @@ -96,7 +94,7 @@ public function __construct(string $appName, protected function configure(): void { $this ->setName('preview:pre-generate') - ->setDescription('Pre generate only images that have been added or changed since the last regular run'); + ->setDescription('Pre generate previews'); } protected function execute(InputInterface $input, OutputInterface $output): int { @@ -104,13 +102,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Encryption is enabled. Aborted.'); return 1; } - + /* + this locks it to only be run once if ($this->checkAlreadyRunning()) { $output->writeln('Command is already running.'); return 2; } $this->setPID(); + */ // Set timestamp output $formatter = new TimestampFormatter($this->config, $output->getFormatter()); @@ -123,37 +123,41 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $this->startProcessing(); + /* $this->clearPID(); + */ return 0; } private function startProcessing(): void { + // random sleep between 0 and 50ms to avoid collision between 2 processes + usleep(rand(0,50000)); + while (true) { $qb = $this->connection->getQueryBuilder(); - $qb->select('*') + $row = $qb->select('*') ->from('preview_generation') ->orderBy('id') - ->setMaxResults(1000); - $cursor = $qb->execute(); - $rows = $cursor->fetchAll(); - $cursor->closeCursor(); + ->where($qb->expr()->eq('locked', $qb->createNamedParameter(false))) + ->setMaxResults(1) + ->execute() + ->fetch(); - if ($rows === []) { + if ($row === false) { break; } - foreach ($rows as $row) { - /* - * First delete the row so that if preview generation fails for some reason - * the next run can just continue - */ - $qb = $this->connection->getQueryBuilder(); - $qb->delete('preview_generation') - ->where($qb->expr()->eq('id', $qb->createNamedParameter($row['id']))); - $qb->execute(); - + $qb->update('preview_generation') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($row['id']))) + ->set('locked', $qb->createNamedParameter(true)) + ->execute(); + try { $this->processRow($row); + } finally { + $qb->delete('preview_generation') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($row['id']))) + ->execute(); } } } @@ -209,6 +213,10 @@ private function processFile(File $file): void { $this->previewGenerator->generatePreviews($file, $this->specifications); } catch (NotFoundException $e) { // Maybe log that previews could not be generated? + if ($this->output->getVerbosity() > OutputInterface::VERBOSITY_VERBOSE) { + $error = $e->getMessage(); + $this->output->writeln("${error} " . $file->getPath() . " not found."); + } } catch (\InvalidArgumentException|GenericFileException $e) { $class = $e::class; $error = $e->getMessage(); diff --git a/lib/Migration/Version020200Date20190608205303.php b/lib/Migration/Version020200Date20190608205303.php new file mode 100644 index 0000000..cc560d2 --- /dev/null +++ b/lib/Migration/Version020200Date20190608205303.php @@ -0,0 +1,53 @@ + + * + * @author Ignacio Nunez + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\PreviewGenerator\Migration; + +use OCP\DB\ISchemaWrapper; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; +use Doctrine\DBAL\Types\Type; + +class Version020200Date20190608205303 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + $table = $schema->getTable('preview_generation'); + + if (!$table->hasColumn('locked')) { + $table->addColumn('locked', Type::BOOLEAN, [ + 'notnull' => true, + 'default' => 0, + ]); + } + return $schema; + } +} \ No newline at end of file