diff --git a/lib/Command/AwaitTaskTrait.php b/lib/Command/AwaitTaskTrait.php
new file mode 100644
index 00000000..a8abf6c3
--- /dev/null
+++ b/lib/Command/AwaitTaskTrait.php
@@ -0,0 +1,109 @@
+
+ */
+ public function getSubscribedSignals(): array {
+ // mirror Symfony's own precondition: subscribing without a signal registry throws
+ if (!\defined('SIGINT') || !SignalRegistry::isSupported()) {
+ return [];
+ }
+
+ return [SIGINT, SIGTERM];
+ }
+
+ public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false {
+ $taskId = $this->awaitTaskId;
+ $this->awaitTaskId = null;
+
+ if ($taskId !== null && $this->awaitManager !== null) {
+ try {
+ $this->awaitManager->cancelTask($taskId);
+ $this->awaitOutput?->writeln('Cancelled task ' . $taskId . '');
+ } catch (TaskProcessingException $e) {
+ $this->awaitOutput?->writeln('Failed to cancel task ' . $taskId . ': ' . $e->getMessage() . '');
+ }
+ }
+
+ // the task produced no result, so never report success to the caller
+ return 128 + $signal;
+ }
+
+ /**
+ * Schedules the task and blocks until it reaches a terminal state.
+ *
+ * @return int the exit code of the command
+ */
+ private function scheduleAndAwaitTask(IManager $manager, Task $task, OutputInterface $output): int {
+ $this->awaitManager = $manager;
+ $this->awaitOutput = $output;
+
+ $manager->scheduleTask($task);
+ $taskId = $task->getId();
+ if ($taskId === null) {
+ $output->writeln('Task schedule failed, taskId is null');
+ return 1;
+ }
+ $this->awaitTaskId = $taskId;
+
+ try {
+ // STATUS_UNKNOWN is terminal as well: the task will never make progress from there
+ $terminalStatuses = [
+ Task::STATUS_SUCCESSFUL,
+ Task::STATUS_FAILED,
+ Task::STATUS_CANCELLED,
+ Task::STATUS_UNKNOWN,
+ ];
+
+ while (!in_array(($task = $manager->getTask($taskId))->getStatus(), $terminalStatuses, true)) {
+ sleep(2);
+ }
+ } catch (NotFoundException $e) {
+ $output->writeln('Task ' . $taskId . ' has disappeared: ' . $e->getMessage() . '');
+ return 1;
+ } finally {
+ $this->awaitTaskId = null;
+ }
+
+ switch ($task->getStatus()) {
+ case Task::STATUS_SUCCESSFUL:
+ $output->writeln(var_export($task->getOutput(), true));
+ return 0;
+ case Task::STATUS_CANCELLED:
+ $output->writeln('Task ' . $taskId . ' was cancelled');
+ return 1;
+ case Task::STATUS_UNKNOWN:
+ $output->writeln('Task ' . $taskId . ' is in an unknown state');
+ return 1;
+ default:
+ $output->writeln('' . ($task->getErrorMessage() ?? '(empty error message)') . '');
+ return 1;
+ }
+ }
+}
diff --git a/lib/Command/Prompt.php b/lib/Command/Prompt.php
index f343d74a..85fee0e2 100644
--- a/lib/Command/Prompt.php
+++ b/lib/Command/Prompt.php
@@ -12,12 +12,15 @@
use OCP\TaskProcessing\IManager;
use OCP\TaskProcessing\Task;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Command\SignalableCommandInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class Prompt extends Command {
+class Prompt extends Command implements SignalableCommandInterface {
+ use AwaitTaskTrait;
+
private const CONTEXT_CHAT_TASK_TYPE_ID = Application::APP_ID . ':context_chat';
public function __construct(
@@ -106,22 +109,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
], 'context_chat', $userId);
}
- $this->taskProcessingManager->scheduleTask($task);
- $taskId = $task->getId();
- if ($taskId === null) {
- $output->writeln('Task schedule failed, taskId is null');
- return 1;
- }
-
- while (!in_array(($task = $this->taskProcessingManager->getTask($taskId))->getStatus(), [Task::STATUS_FAILED, Task::STATUS_SUCCESSFUL], true)) {
- sleep(2);
- }
- if ($task->getStatus() === Task::STATUS_SUCCESSFUL) {
- $output->writeln(var_export($task->getOutput(), true));
- return 0;
- } else {
- $output->writeln('' . ($task->getErrorMessage() ?? '(empty error message)') . '');
- return 1;
- }
+ return $this->scheduleAndAwaitTask($this->taskProcessingManager, $task, $output);
}
}
diff --git a/lib/Command/Search.php b/lib/Command/Search.php
index f5bb156f..af713624 100644
--- a/lib/Command/Search.php
+++ b/lib/Command/Search.php
@@ -12,12 +12,15 @@
use OCP\TaskProcessing\IManager;
use OCP\TaskProcessing\Task;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Command\SignalableCommandInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
-class Search extends Command {
+class Search extends Command implements SignalableCommandInterface {
+ use AwaitTaskTrait;
+
private const SEARCH_TASK_TYPE_ID = Application::APP_ID . ':context_chat_search';
public function __construct(
@@ -59,6 +62,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$limit = $input->getOption('limit');
$contextProviders = $input->getOption('context-providers');
+ if ($limit !== null) {
+ $limit = filter_var($limit, FILTER_VALIDATE_INT);
+ if ($limit === false || $limit < 1) {
+ $output->writeln('The limit option must be a positive integer');
+ return 1;
+ }
+ }
+
if (!empty($contextProviders)) {
$contextProviders = preg_replace('/\s*,+\s*/', ',', $contextProviders);
if ($contextProviders === null) {
@@ -87,22 +98,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
], Application::APP_ID, $userId);
}
- $this->taskProcessingManager->scheduleTask($task);
- $taskId = $task->getId();
- if ($taskId === null) {
- $output->writeln('Task schedule failed, taskId is null');
- return 1;
- }
-
- while (!in_array(($task = $this->taskProcessingManager->getTask($taskId))->getStatus(), [Task::STATUS_FAILED, Task::STATUS_SUCCESSFUL], true)) {
- sleep(2);
- }
- if ($task->getStatus() === Task::STATUS_SUCCESSFUL) {
- $output->writeln(var_export($task->getOutput(), true));
- return 0;
- } else {
- $output->writeln('' . ($task->getErrorMessage() ?? '(empty error message)') . '');
- return 1;
- }
+ return $this->scheduleAndAwaitTask($this->taskProcessingManager, $task, $output);
}
}