aboutsummaryrefslogtreecommitdiffstats
path: root/core/Command
diff options
context:
space:
mode:
Diffstat (limited to 'core/Command')
-rw-r--r--core/Command/Config/Preset.php10
-rw-r--r--core/Command/Preview/Generate.php132
-rw-r--r--core/Command/TaskProcessing/Cleanup.php93
-rw-r--r--core/Command/TaskProcessing/EnabledCommand.php10
-rw-r--r--core/Command/TaskProcessing/GetCommand.php2
-rw-r--r--core/Command/TaskProcessing/ListCommand.php2
-rw-r--r--core/Command/TaskProcessing/Statistics.php2
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