diff options
Diffstat (limited to 'core/Command')
-rw-r--r-- | core/Command/Config/Preset.php | 10 | ||||
-rw-r--r-- | core/Command/Preview/Generate.php | 132 | ||||
-rw-r--r-- | core/Command/TaskProcessing/Cleanup.php | 93 | ||||
-rw-r--r-- | core/Command/TaskProcessing/EnabledCommand.php | 10 | ||||
-rw-r--r-- | core/Command/TaskProcessing/GetCommand.php | 2 | ||||
-rw-r--r-- | core/Command/TaskProcessing/ListCommand.php | 2 | ||||
-rw-r--r-- | core/Command/TaskProcessing/Statistics.php | 2 |
7 files changed, 164 insertions, 87 deletions
diff --git a/core/Command/Config/Preset.php b/core/Command/Config/Preset.php index 4f0278896db..ebd8aaa5cdf 100644 --- a/core/Command/Config/Preset.php +++ b/core/Command/Config/Preset.php @@ -8,10 +8,9 @@ declare(strict_types=1); */ namespace OC\Core\Command\Config; -use OC\Config\ConfigManager; +use OC\Config\PresetManager; use OC\Core\Command\Base; use OCP\Config\Lexicon\Preset as ConfigLexiconPreset; -use OCP\IConfig; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -19,8 +18,7 @@ use Symfony\Component\Console\Output\OutputInterface; class Preset extends Base { public function __construct( - private readonly IConfig $config, - private readonly ConfigManager $configManager, + private readonly PresetManager $presetManager, ) { parent::__construct(); } @@ -49,10 +47,10 @@ class Preset extends Base { return self::INVALID; } - $this->configManager->setLexiconPreset($preset); + $this->presetManager->setLexiconPreset($preset); } - $current = ConfigLexiconPreset::tryFrom($this->config->getSystemValueInt(ConfigManager::PRESET_CONFIGKEY, 0)) ?? ConfigLexiconPreset::NONE; + $current = $this->presetManager->getLexiconPreset(); $this->writeArrayInOutputFormat($input, $output, [$current->name], 'current preset: '); return self::SUCCESS; } diff --git a/core/Command/Preview/Generate.php b/core/Command/Preview/Generate.php index 86301834fbe..222c42f613b 100644 --- a/core/Command/Preview/Generate.php +++ b/core/Command/Preview/Generate.php @@ -8,10 +8,11 @@ declare(strict_types=1); namespace OC\Core\Command\Preview; -use OC\Core\Command\Info\FileUtils; use OCP\Files\Config\IUserMountCache; use OCP\Files\File; use OCP\Files\IRootFolder; +use OCP\Files\Node; +use OCP\Files\NotFoundException; use OCP\IPreview; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -19,107 +20,66 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -/** - * Generate a preview for a file from the command-line. - * - * Useful in automations and for troubleshooting preview generation issues. - * - * @since 27.0.0 - */ class Generate extends Command { public function __construct( private IRootFolder $rootFolder, private IUserMountCache $userMountCache, private IPreview $previewManager, - private FileUtils $fileUtils, ) { parent::__construct(); } - protected function configure(): void { + protected function configure() { $this ->setName('preview:generate') - ->setDescription('Generates a preview for a file') - ->setHelp('Generates a preview for an individual file for automation or troubleshooting purposes.') - ->addArgument( - 'file', - InputArgument::REQUIRED, - 'file id or Nextcloud path' - ) - ->addOption( - 'size', - 's', - InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, - 'preview size(s) to generate in pixels, defaults to 64x64', - ['64x64'] - ) - ->addOption( - 'crop', - 'c', - InputOption::VALUE_NONE, - 'crop the previews instead of maintaining aspect ratio' - ) - ->addOption( - 'mode', - 'm', - InputOption::VALUE_REQUIRED, - 'mode for generating uncropped previews, \'cover\' or \'fill\'', - IPreview::MODE_FILL - ) - ; + ->setDescription('generate a preview for a file') + ->addArgument('file', InputArgument::REQUIRED, 'path or fileid of the file to generate the preview for') + ->addOption('size', 's', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'size to generate the preview for in pixels, defaults to 64x64', ['64x64']) + ->addOption('crop', 'c', InputOption::VALUE_NONE, 'crop the previews instead of maintaining aspect ratio') + ->addOption('mode', 'm', InputOption::VALUE_REQUIRED, "mode for generating uncropped previews, 'cover' or 'fill'", IPreview::MODE_FILL); } protected function execute(InputInterface $input, OutputInterface $output): int { - // parse and check `file` argument value (can be a file id or path to a specific file relative to the data directory $fileInput = $input->getArgument('file'); - $node = $this->fileUtils->getNode($fileInput); - if (!$node) { - $output->writeln("<error>File ($fileInput) does not exist</error>"); - return self::FAILURE; - } - if (!$node instanceof File) { - $output->writeln("<error>specified file ($fileInput) is not a file (did you specify a folder by accident?)</error>"); - return self::INVALID; - } - // No point in continuing if there isn't a configured preview provider for the file - if (!$this->previewManager->isAvailable($node)) { - $output->writeln('<error>File of type ' . $node->getMimetype() . ' does not have a preview generator configured.</error>'); - return self::FAILURE; - } - - // parse and check `size` option value(s) ("64x64" if not specified) - $sizeInput = $input->getOption('size'); - // parse size option value(s) - // (e.g. ["128"] or ["128x128"] or even ["64x64", "128x128"]) - $sizes = array_map(function (string $size) use ($output): array|null { + $sizes = $input->getOption('size'); + $sizes = array_map(function (string $size) use ($output) { if (str_contains($size, 'x')) { $sizeParts = explode('x', $size, 2); } else { $sizeParts = [$size, $size]; } if (!is_numeric($sizeParts[0]) || !is_numeric($sizeParts[1] ?? null)) { - // output error here to be able to inform which one size entry caused it - $output->writeln("<error>Size ($size) is invalid</error>"); + $output->writeln("<error>Invalid size $size</error>"); return null; } + return array_map('intval', $sizeParts); - }, $sizeInput); + }, $sizes); if (in_array(null, $sizes)) { - // error output already provided so no need for it here - return self::FAILURE; + return 1; } - // parse the `crop` option value - $crop = $input->getOption('crop'); - - // parse and check the `mode` option value $mode = $input->getOption('mode'); if ($mode !== IPreview::MODE_FILL && $mode !== IPreview::MODE_COVER) { - $output->writeln("<error>Mode ($mode) is invalid</error>"); - return self::INVALID; + $output->writeln("<error>Invalid mode $mode</error>"); + return 1; + } + $crop = $input->getOption('crop'); + $file = $this->getFile($fileInput); + if (!$file) { + $output->writeln("<error>File $fileInput not found</error>"); + return 1; + } + if (!$file instanceof File) { + $output->writeln("<error>Can't generate previews for folders</error>"); + return 1; + } + + if (!$this->previewManager->isAvailable($file)) { + $output->writeln('<error>No preview generator available for file of type' . $file->getMimetype() . '</error>'); + return 1; } - // generate the specifification(s) of the preview size(s) generate $specifications = array_map(function (array $sizes) use ($crop, $mode) { return [ 'width' => $sizes[0], @@ -129,12 +89,30 @@ class Generate extends Command { ]; }, $sizes); - // generate the actual requested previews - $this->previewManager->generatePreviews($node, $specifications); - - // inform the user what we did if we were successful - $output->writeln('Generated <info>' . count($specifications) . '</info> preview(s)'); + $this->previewManager->generatePreviews($file, $specifications); + if (count($specifications) > 1) { + $output->writeln('generated <info>' . count($specifications) . '</info> previews'); + } else { + $output->writeln('preview generated'); + } + return 0; + } - return self::SUCCESS; + private function getFile(string $fileInput): ?Node { + if (is_numeric($fileInput)) { + $mounts = $this->userMountCache->getMountsForFileId((int)$fileInput); + if (!$mounts) { + return null; + } + $mount = $mounts[0]; + $userFolder = $this->rootFolder->getUserFolder($mount->getUser()->getUID()); + return $userFolder->getFirstNodeById((int)$fileInput); + } else { + try { + return $this->rootFolder->get($fileInput); + } catch (NotFoundException $e) { + return null; + } + } } } diff --git a/core/Command/TaskProcessing/Cleanup.php b/core/Command/TaskProcessing/Cleanup.php new file mode 100644 index 00000000000..2ed2cbdec94 --- /dev/null +++ b/core/Command/TaskProcessing/Cleanup.php @@ -0,0 +1,93 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OC\Core\Command\TaskProcessing; + +use OC\Core\Command\Base; +use OC\TaskProcessing\Db\TaskMapper; +use OC\TaskProcessing\Manager; +use OCP\Files\AppData\IAppDataFactory; +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class Cleanup extends Base { + private \OCP\Files\IAppData $appData; + + public function __construct( + protected Manager $taskProcessingManager, + private TaskMapper $taskMapper, + private LoggerInterface $logger, + IAppDataFactory $appDataFactory, + ) { + parent::__construct(); + $this->appData = $appDataFactory->get('core'); + } + + protected function configure() { + $this + ->setName('taskprocessing:task:cleanup') + ->setDescription('cleanup old tasks') + ->addArgument( + 'maxAgeSeconds', + InputArgument::OPTIONAL, + // default is not defined as an argument default value because we want to show a nice "4 months" value + 'delete tasks that are older than this number of seconds, defaults to ' . Manager::MAX_TASK_AGE_SECONDS . ' (4 months)', + ); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $maxAgeSeconds = $input->getArgument('maxAgeSeconds') ?? Manager::MAX_TASK_AGE_SECONDS; + $output->writeln('<comment>Cleanup up tasks older than ' . $maxAgeSeconds . ' seconds and the related output files</comment>'); + + $taskIdsToCleanup = []; + try { + $fileCleanupGenerator = $this->taskProcessingManager->cleanupTaskProcessingTaskFiles($maxAgeSeconds); + foreach ($fileCleanupGenerator as $cleanedUpEntry) { + $output->writeln( + "<info>\t - " . 'Deleted appData/core/TaskProcessing/' . $cleanedUpEntry['file_name'] + . ' (fileId: ' . $cleanedUpEntry['file_id'] . ', taskId: ' . $cleanedUpEntry['task_id'] . ')</info>' + ); + } + $taskIdsToCleanup = $fileCleanupGenerator->getReturn(); + } catch (\Exception $e) { + $this->logger->warning('Failed to delete stale task processing tasks files', ['exception' => $e]); + $output->writeln('<warning>Failed to delete stale task processing tasks files</warning>'); + } + try { + $deletedTaskCount = $this->taskMapper->deleteOlderThan($maxAgeSeconds); + foreach ($taskIdsToCleanup as $taskId) { + $output->writeln("<info>\t - " . 'Deleted task ' . $taskId . ' from the database</info>'); + } + $output->writeln("<comment>\t - " . 'Deleted ' . $deletedTaskCount . ' tasks from the database</comment>'); + } catch (\OCP\DB\Exception $e) { + $this->logger->warning('Failed to delete stale task processing tasks', ['exception' => $e]); + $output->writeln('<warning>Failed to delete stale task processing tasks</warning>'); + } + try { + $textToImageDeletedFileNames = $this->taskProcessingManager->clearFilesOlderThan($this->appData->getFolder('text2image'), $maxAgeSeconds); + foreach ($textToImageDeletedFileNames as $entry) { + $output->writeln("<info>\t - " . 'Deleted appData/core/text2image/' . $entry . '</info>'); + } + } catch (\OCP\Files\NotFoundException $e) { + // noop + } + try { + $audioToTextDeletedFileNames = $this->taskProcessingManager->clearFilesOlderThan($this->appData->getFolder('audio2text'), $maxAgeSeconds); + foreach ($audioToTextDeletedFileNames as $entry) { + $output->writeln("<info>\t - " . 'Deleted appData/core/audio2text/' . $entry . '</info>'); + } + } catch (\OCP\Files\NotFoundException $e) { + // noop + } + + return 0; + } +} diff --git a/core/Command/TaskProcessing/EnabledCommand.php b/core/Command/TaskProcessing/EnabledCommand.php index 7195d19a7a4..a99e3e001b9 100644 --- a/core/Command/TaskProcessing/EnabledCommand.php +++ b/core/Command/TaskProcessing/EnabledCommand.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -7,7 +9,7 @@ namespace OC\Core\Command\TaskProcessing; use OC\Core\Command\Base; -use OCP\IConfig; +use OCP\IAppConfig; use OCP\TaskProcessing\IManager; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -16,7 +18,7 @@ use Symfony\Component\Console\Output\OutputInterface; class EnabledCommand extends Base { public function __construct( protected IManager $taskProcessingManager, - private IConfig $config, + private IAppConfig $appConfig, ) { parent::__construct(); } @@ -41,7 +43,7 @@ class EnabledCommand extends Base { protected function execute(InputInterface $input, OutputInterface $output): int { $enabled = (bool)$input->getArgument('enabled'); $taskType = $input->getArgument('task-type-id'); - $json = $this->config->getAppValue('core', 'ai.taskprocessing_type_preferences'); + $json = $this->appConfig->getValueString('core', 'ai.taskprocessing_type_preferences', lazy: true); try { if ($json === '') { $taskTypeSettings = []; @@ -51,7 +53,7 @@ class EnabledCommand extends Base { $taskTypeSettings[$taskType] = $enabled; - $this->config->setAppValue('core', 'ai.taskprocessing_type_preferences', json_encode($taskTypeSettings)); + $this->appConfig->setValueString('core', 'ai.taskprocessing_type_preferences', json_encode($taskTypeSettings), lazy: true); $this->writeArrayInOutputFormat($input, $output, $taskTypeSettings); return 0; } catch (\JsonException $e) { diff --git a/core/Command/TaskProcessing/GetCommand.php b/core/Command/TaskProcessing/GetCommand.php index 5c4fd17f2f8..f97556281a1 100644 --- a/core/Command/TaskProcessing/GetCommand.php +++ b/core/Command/TaskProcessing/GetCommand.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/core/Command/TaskProcessing/ListCommand.php b/core/Command/TaskProcessing/ListCommand.php index 81eb258d35d..50a694c1a6e 100644 --- a/core/Command/TaskProcessing/ListCommand.php +++ b/core/Command/TaskProcessing/ListCommand.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/core/Command/TaskProcessing/Statistics.php b/core/Command/TaskProcessing/Statistics.php index 86478b34db1..6aa4cd5bf52 100644 --- a/core/Command/TaskProcessing/Statistics.php +++ b/core/Command/TaskProcessing/Statistics.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later |