diff options
author | provokateurin <kate@provokateurin.de> | 2024-05-17 11:54:31 +0200 |
---|---|---|
committer | provokateurin <kate@provokateurin.de> | 2024-07-01 17:11:12 +0200 |
commit | f5ff8136ac3e26cc6b7676dd1f9d307736ad1918 (patch) | |
tree | 9a4121721e4a2fe599897b3090a351a5b72d0844 /core/Controller | |
parent | 5aefdc399eb17a86f3c2b59713ca6448479f99fd (diff) | |
download | nextcloud-server-f5ff8136ac3e26cc6b7676dd1f9d307736ad1918.tar.gz nextcloud-server-f5ff8136ac3e26cc6b7676dd1f9d307736ad1918.zip |
feat(TaskProcessingApi): Add endpoint for getting the next task
Signed-off-by: provokateurin <kate@provokateurin.de>
Diffstat (limited to 'core/Controller')
-rw-r--r-- | core/Controller/TaskProcessingApiController.php | 183 |
1 files changed, 137 insertions, 46 deletions
diff --git a/core/Controller/TaskProcessingApiController.php b/core/Controller/TaskProcessingApiController.php index 383a6d7a31a..2b56ed80ac6 100644 --- a/core/Controller/TaskProcessingApiController.php +++ b/core/Controller/TaskProcessingApiController.php @@ -14,21 +14,29 @@ use OCA\Core\ResponseDefinitions; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\AnonRateLimit; use OCP\AppFramework\Http\Attribute\ApiRoute; +use OCP\AppFramework\Http\Attribute\ExAppRequired; use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\Attribute\UserRateLimit; use OCP\AppFramework\Http\DataDownloadResponse; use OCP\AppFramework\Http\DataResponse; use OCP\Files\File; +use OCP\Files\GenericFileException; use OCP\Files\IRootFolder; +use OCP\Files\NotPermittedException; use OCP\IL10N; use OCP\IRequest; +use OCP\Lock\LockedException; use OCP\TaskProcessing\EShapeType; use OCP\TaskProcessing\Exception\Exception; +use OCP\TaskProcessing\Exception\NotFoundException; +use OCP\TaskProcessing\Exception\PreConditionNotMetException; use OCP\TaskProcessing\Exception\UnauthorizedException; use OCP\TaskProcessing\Exception\ValidationException; +use OCP\TaskProcessing\IManager; use OCP\TaskProcessing\ShapeDescriptor; use OCP\TaskProcessing\Task; +use RuntimeException; /** * @psalm-import-type CoreTaskProcessingTask from ResponseDefinitions @@ -36,11 +44,11 @@ use OCP\TaskProcessing\Task; */ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { public function __construct( - string $appName, - IRequest $request, - private \OCP\TaskProcessing\IManager $taskProcessingManager, - private IL10N $l, - private ?string $userId, + string $appName, + IRequest $request, + private IManager $taskProcessingManager, + private IL10N $l, + private ?string $userId, private IRootFolder $rootFolder, ) { parent::__construct($appName, $request); @@ -109,13 +117,13 @@ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { return new DataResponse([ 'task' => $json, ]); - } catch (\OCP\TaskProcessing\Exception\PreConditionNotMetException) { + } catch (PreConditionNotMetException) { return new DataResponse(['message' => $this->l->t('The given provider is not available')], Http::STATUS_PRECONDITION_FAILED); } catch (ValidationException $e) { return new DataResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST); - } catch (UnauthorizedException $e) { + } catch (UnauthorizedException) { return new DataResponse(['message' => 'User does not have access to the files mentioned in the task input'], Http::STATUS_UNAUTHORIZED); - } catch (\OCP\TaskProcessing\Exception\Exception $e) { + } catch (Exception) { return new DataResponse(['message' => 'Internal server error'], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -144,9 +152,9 @@ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { return new DataResponse([ 'task' => $json, ]); - } catch (\OCP\TaskProcessing\Exception\NotFoundException $e) { + } catch (NotFoundException) { return new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND); - } catch (\RuntimeException $e) { + } catch (RuntimeException) { return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -169,9 +177,9 @@ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { $this->taskProcessingManager->deleteTask($task); return new DataResponse(null); - } catch (\OCP\TaskProcessing\Exception\NotFoundException $e) { + } catch (NotFoundException) { return new DataResponse(null); - } catch (\OCP\TaskProcessing\Exception\Exception $e) { + } catch (Exception) { return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -199,7 +207,7 @@ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { return new DataResponse([ 'tasks' => $json, ]); - } catch (Exception $e) { + } catch (Exception) { return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -226,7 +234,7 @@ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { return new DataResponse([ 'tasks' => $json, ]); - } catch (Exception $e) { + } catch (Exception) { return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -247,37 +255,72 @@ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { public function getFileContents(int $taskId, int $fileId): Http\DataDownloadResponse|DataResponse { try { $task = $this->taskProcessingManager->getUserTask($taskId, $this->userId); - $ids = $this->extractFileIdsFromTask($task); - if (!in_array($fileId, $ids)) { - return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND); - } - $node = $this->rootFolder->getFirstNodeById($fileId); - if ($node === null) { - $node = $this->rootFolder->getFirstNodeByIdInPath($fileId, '/' . $this->rootFolder->getAppDataDirectoryName() . '/'); - if (!$node instanceof File) { - throw new \OCP\TaskProcessing\Exception\NotFoundException('Node is not a file'); - } - } elseif (!$node instanceof File) { - throw new \OCP\TaskProcessing\Exception\NotFoundException('Node is not a file'); - } - return new Http\DataDownloadResponse($node->getContent(), $node->getName(), $node->getMimeType()); - } catch (\OCP\TaskProcessing\Exception\NotFoundException $e) { + return $this->getFileContentsInternal($task, $fileId); + } catch (NotFoundException) { + return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND); + } catch (Exception) { + return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); + } + } + + /** + * Returns the contents of a file referenced in a task(ExApp route version) + * + * @param int $taskId The id of the task + * @param int $fileId The file id of the file to retrieve + * @return DataDownloadResponse<Http::STATUS_OK, string, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}> + * + * 200: File content returned + * 404: Task or file not found + */ + #[ExAppRequired] + #[ApiRoute(verb: 'GET', url: '/tasks_provider/{taskId}/file/{fileId}', root: '/taskprocessing')] + public function getFileContentsExApp(int $taskId, int $fileId): Http\DataDownloadResponse|DataResponse { + try { + $task = $this->taskProcessingManager->getTask($taskId); + return $this->getFileContentsInternal($task, $fileId); + } catch (NotFoundException) { return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND); - } catch (Exception $e) { + } catch (Exception) { return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); } } /** + * @throws NotPermittedException + * @throws NotFoundException + * @throws GenericFileException + * @throws LockedException + * + * @return DataDownloadResponse<Http::STATUS_OK, string, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}> + */ + private function getFileContentsInternal(Task $task, int $fileId): Http\DataDownloadResponse|DataResponse { + $ids = $this->extractFileIdsFromTask($task); + if (!in_array($fileId, $ids)) { + return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND); + } + $node = $this->rootFolder->getFirstNodeById($fileId); + if ($node === null) { + $node = $this->rootFolder->getFirstNodeByIdInPath($fileId, '/' . $this->rootFolder->getAppDataDirectoryName() . '/'); + if (!$node instanceof File) { + throw new NotFoundException('Node is not a file'); + } + } elseif (!$node instanceof File) { + throw new NotFoundException('Node is not a file'); + } + return new Http\DataDownloadResponse($node->getContent(), $node->getName(), $node->getMimeType()); + } + + /** * @param Task $task * @return list<int> - * @throws \OCP\TaskProcessing\Exception\NotFoundException + * @throws NotFoundException */ private function extractFileIdsFromTask(Task $task): array { $ids = []; $taskTypes = $this->taskProcessingManager->getAvailableTaskTypes(); if (!isset($taskTypes[$task->getTaskTypeId()])) { - throw new \OCP\TaskProcessing\Exception\NotFoundException('Could not find task type'); + throw new NotFoundException('Could not find task type'); } $taskType = $taskTypes[$task->getTaskTypeId()]; foreach ($taskType['inputShape'] + $taskType['optionalInputShape'] as $key => $descriptor) { @@ -317,12 +360,12 @@ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { * 200: Progress updated successfully * 404: Task not found */ - #[NoAdminRequired] - #[ApiRoute(verb: 'POST', url: '/tasks/{taskId}/progress', root: '/taskprocessing')] + #[ExAppRequired] + #[ApiRoute(verb: 'POST', url: '/tasks_provider/{taskId}/progress', root: '/taskprocessing')] public function setProgress(int $taskId, float $progress): DataResponse { try { $this->taskProcessingManager->setTaskProgress($taskId, $progress); - $task = $this->taskProcessingManager->getUserTask($taskId, $this->userId); + $task = $this->taskProcessingManager->getTask($taskId); /** @var CoreTaskProcessingTask $json */ $json = $task->jsonSerialize(); @@ -330,9 +373,9 @@ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { return new DataResponse([ 'task' => $json, ]); - } catch (\OCP\TaskProcessing\Exception\NotFoundException $e) { + } catch (NotFoundException) { return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND); - } catch (Exception $e) { + } catch (Exception) { return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -348,15 +391,13 @@ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { * 200: Result updated successfully * 404: Task not found */ - #[NoAdminRequired] - #[ApiRoute(verb: 'POST', url: '/tasks/{taskId}/result', root: '/taskprocessing')] + #[ExAppRequired] + #[ApiRoute(verb: 'POST', url: '/tasks_provider/{taskId}/result', root: '/taskprocessing')] public function setResult(int $taskId, ?array $output = null, ?string $errorMessage = null): DataResponse { try { - // Check if the current user can access the task - $this->taskProcessingManager->getUserTask($taskId, $this->userId); // set result $this->taskProcessingManager->setTaskResult($taskId, $errorMessage, $output); - $task = $this->taskProcessingManager->getUserTask($taskId, $this->userId); + $task = $this->taskProcessingManager->getTask($taskId); /** @var CoreTaskProcessingTask $json */ $json = $task->jsonSerialize(); @@ -364,9 +405,9 @@ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { return new DataResponse([ 'task' => $json, ]); - } catch (\OCP\TaskProcessing\Exception\NotFoundException $e) { + } catch (NotFoundException) { return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND); - } catch (Exception $e) { + } catch (Exception) { return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); } } @@ -396,9 +437,59 @@ class TaskProcessingApiController extends \OCP\AppFramework\OCSController { return new DataResponse([ 'task' => $json, ]); - } catch (\OCP\TaskProcessing\Exception\NotFoundException $e) { + } catch (NotFoundException) { return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND); - } catch (Exception $e) { + } catch (Exception) { + return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); + } + } + + /** + * Returns the next scheduled task for the taskTypeId + * + * @param list<string> $providerIds The ids of the providers + * @param list<string> $taskTypeIds The ids of the task types + * @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask, provider: array{name: string}}, array{}>|DataResponse<Http::STATUS_NO_CONTENT, null, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}> + * + * 200: Task returned + * 204: No task found + */ + #[ExAppRequired] + #[ApiRoute(verb: 'GET', url: '/tasks_provider/next', root: '/taskprocessing')] + public function getNextScheduledTask(array $providerIds, array $taskTypeIds): DataResponse { + try { + // restrict $providerIds to providers that are configured as preferred for the passed task types + $providerIds = array_values(array_intersect(array_unique(array_map(fn ($taskTypeId) => $this->taskProcessingManager->getPreferredProvider($taskTypeId)->getId(), $taskTypeIds)), $providerIds)); + // restrict $taskTypeIds to task types that can actually be run by one of the now restricted providers + $taskTypeIds = array_values(array_filter($taskTypeIds, fn ($taskTypeId) => in_array($this->taskProcessingManager->getPreferredProvider($taskTypeId)->getId(), $providerIds, true))); + if (count($providerIds) === 0 || count($taskTypeIds) === 0) { + throw new NotFoundException(); + } + + $taskIdsToIgnore = []; + while (true) { + $task = $this->taskProcessingManager->getNextScheduledTask($taskTypeIds, $taskIdsToIgnore); + $provider = $this->taskProcessingManager->getPreferredProvider($task->getTaskTypeId()); + if (in_array($provider->getId(), $providerIds, true)) { + if ($this->taskProcessingManager->lockTask($task)) { + break; + } + } + $taskIdsToIgnore[] = (int)$task->getId(); + } + + /** @var CoreTaskProcessingTask $json */ + $json = $task->jsonSerialize(); + + return new DataResponse([ + 'task' => $json, + 'provider' => [ + 'name' => $provider->getId(), + ], + ]); + } catch (NotFoundException) { + return new DataResponse(null, Http::STATUS_NO_CONTENT); + } catch (Exception) { return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); } } |