aboutsummaryrefslogtreecommitdiffstats
path: root/core/Controller
diff options
context:
space:
mode:
authorprovokateurin <kate@provokateurin.de>2024-05-17 11:54:31 +0200
committerprovokateurin <kate@provokateurin.de>2024-07-01 17:11:12 +0200
commitf5ff8136ac3e26cc6b7676dd1f9d307736ad1918 (patch)
tree9a4121721e4a2fe599897b3090a351a5b72d0844 /core/Controller
parent5aefdc399eb17a86f3c2b59713ca6448479f99fd (diff)
downloadnextcloud-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.php183
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);
}
}