Skip to content

Commit ed62b4c

Browse files
committed
feat(TaskProcessingApi): Add endpoint for getting the next task
Signed-off-by: provokateurin <kate@provokateurin.de>
1 parent 5aefdc3 commit ed62b4c

File tree

10 files changed

+9760
-410
lines changed

10 files changed

+9760
-410
lines changed

core/Controller/TaskProcessingApiController.php

Lines changed: 137 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,41 @@
1414
use OCP\AppFramework\Http;
1515
use OCP\AppFramework\Http\Attribute\AnonRateLimit;
1616
use OCP\AppFramework\Http\Attribute\ApiRoute;
17+
use OCP\AppFramework\Http\Attribute\ExAppRequired;
1718
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
1819
use OCP\AppFramework\Http\Attribute\PublicPage;
1920
use OCP\AppFramework\Http\Attribute\UserRateLimit;
2021
use OCP\AppFramework\Http\DataDownloadResponse;
2122
use OCP\AppFramework\Http\DataResponse;
2223
use OCP\Files\File;
24+
use OCP\Files\GenericFileException;
2325
use OCP\Files\IRootFolder;
26+
use OCP\Files\NotPermittedException;
2427
use OCP\IL10N;
2528
use OCP\IRequest;
29+
use OCP\Lock\LockedException;
2630
use OCP\TaskProcessing\EShapeType;
2731
use OCP\TaskProcessing\Exception\Exception;
32+
use OCP\TaskProcessing\Exception\NotFoundException;
33+
use OCP\TaskProcessing\Exception\PreConditionNotMetException;
2834
use OCP\TaskProcessing\Exception\UnauthorizedException;
2935
use OCP\TaskProcessing\Exception\ValidationException;
36+
use OCP\TaskProcessing\IManager;
3037
use OCP\TaskProcessing\ShapeDescriptor;
3138
use OCP\TaskProcessing\Task;
39+
use RuntimeException;
3240

3341
/**
3442
* @psalm-import-type CoreTaskProcessingTask from ResponseDefinitions
3543
* @psalm-import-type CoreTaskProcessingTaskType from ResponseDefinitions
3644
*/
3745
class TaskProcessingApiController extends \OCP\AppFramework\OCSController {
3846
public function __construct(
39-
string $appName,
40-
IRequest $request,
41-
private \OCP\TaskProcessing\IManager $taskProcessingManager,
42-
private IL10N $l,
43-
private ?string $userId,
47+
string $appName,
48+
IRequest $request,
49+
private IManager $taskProcessingManager,
50+
private IL10N $l,
51+
private ?string $userId,
4452
private IRootFolder $rootFolder,
4553
) {
4654
parent::__construct($appName, $request);
@@ -109,13 +117,13 @@ public function schedule(array $input, string $type, string $appId, string $cust
109117
return new DataResponse([
110118
'task' => $json,
111119
]);
112-
} catch (\OCP\TaskProcessing\Exception\PreConditionNotMetException) {
120+
} catch (PreConditionNotMetException) {
113121
return new DataResponse(['message' => $this->l->t('The given provider is not available')], Http::STATUS_PRECONDITION_FAILED);
114122
} catch (ValidationException $e) {
115123
return new DataResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST);
116-
} catch (UnauthorizedException $e) {
124+
} catch (UnauthorizedException) {
117125
return new DataResponse(['message' => 'User does not have access to the files mentioned in the task input'], Http::STATUS_UNAUTHORIZED);
118-
} catch (\OCP\TaskProcessing\Exception\Exception $e) {
126+
} catch (Exception) {
119127
return new DataResponse(['message' => 'Internal server error'], Http::STATUS_INTERNAL_SERVER_ERROR);
120128
}
121129
}
@@ -144,9 +152,9 @@ public function getTask(int $id): DataResponse {
144152
return new DataResponse([
145153
'task' => $json,
146154
]);
147-
} catch (\OCP\TaskProcessing\Exception\NotFoundException $e) {
155+
} catch (NotFoundException) {
148156
return new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
149-
} catch (\RuntimeException $e) {
157+
} catch (RuntimeException) {
150158
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
151159
}
152160
}
@@ -169,9 +177,9 @@ public function deleteTask(int $id): DataResponse {
169177
$this->taskProcessingManager->deleteTask($task);
170178

171179
return new DataResponse(null);
172-
} catch (\OCP\TaskProcessing\Exception\NotFoundException $e) {
180+
} catch (NotFoundException) {
173181
return new DataResponse(null);
174-
} catch (\OCP\TaskProcessing\Exception\Exception $e) {
182+
} catch (Exception) {
175183
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
176184
}
177185
}
@@ -199,7 +207,7 @@ public function listTasksByApp(string $appId, ?string $customId = null): DataRes
199207
return new DataResponse([
200208
'tasks' => $json,
201209
]);
202-
} catch (Exception $e) {
210+
} catch (Exception) {
203211
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
204212
}
205213
}
@@ -226,7 +234,7 @@ public function listTasks(?string $taskType, ?string $customId = null): DataResp
226234
return new DataResponse([
227235
'tasks' => $json,
228236
]);
229-
} catch (Exception $e) {
237+
} catch (Exception) {
230238
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
231239
}
232240
}
@@ -247,37 +255,72 @@ public function listTasks(?string $taskType, ?string $customId = null): DataResp
247255
public function getFileContents(int $taskId, int $fileId): Http\DataDownloadResponse|DataResponse {
248256
try {
249257
$task = $this->taskProcessingManager->getUserTask($taskId, $this->userId);
250-
$ids = $this->extractFileIdsFromTask($task);
251-
if (!in_array($fileId, $ids)) {
252-
return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
253-
}
254-
$node = $this->rootFolder->getFirstNodeById($fileId);
255-
if ($node === null) {
256-
$node = $this->rootFolder->getFirstNodeByIdInPath($fileId, '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
257-
if (!$node instanceof File) {
258-
throw new \OCP\TaskProcessing\Exception\NotFoundException('Node is not a file');
259-
}
260-
} elseif (!$node instanceof File) {
261-
throw new \OCP\TaskProcessing\Exception\NotFoundException('Node is not a file');
262-
}
263-
return new Http\DataDownloadResponse($node->getContent(), $node->getName(), $node->getMimeType());
264-
} catch (\OCP\TaskProcessing\Exception\NotFoundException $e) {
258+
return $this->getFileContentsInternal($task, $fileId);
259+
} catch (NotFoundException) {
260+
return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
261+
} catch (Exception) {
262+
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
263+
}
264+
}
265+
266+
/**
267+
* Returns the contents of a file referenced in a task(ExApp route version)
268+
*
269+
* @param int $taskId The id of the task
270+
* @param int $fileId The file id of the file to retrieve
271+
* @return DataDownloadResponse<Http::STATUS_OK, string, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
272+
*
273+
* 200: File content returned
274+
* 404: Task or file not found
275+
*/
276+
#[ExAppRequired]
277+
#[ApiRoute(verb: 'GET', url: '/tasks_provider/{taskId}/file/{fileId}', root: '/taskprocessing')]
278+
public function getFileContentsExApp(int $taskId, int $fileId): Http\DataDownloadResponse|DataResponse {
279+
try {
280+
$task = $this->taskProcessingManager->getTask($taskId);
281+
return $this->getFileContentsInternal($task, $fileId);
282+
} catch (NotFoundException) {
265283
return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
266-
} catch (Exception $e) {
284+
} catch (Exception) {
267285
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
268286
}
269287
}
270288

289+
/**
290+
* @throws NotPermittedException
291+
* @throws NotFoundException
292+
* @throws GenericFileException
293+
* @throws LockedException
294+
*
295+
* @return DataDownloadResponse<Http::STATUS_OK, string, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
296+
*/
297+
private function getFileContentsInternal(Task $task, int $fileId): Http\DataDownloadResponse|DataResponse {
298+
$ids = $this->extractFileIdsFromTask($task);
299+
if (!in_array($fileId, $ids)) {
300+
return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
301+
}
302+
$node = $this->rootFolder->getFirstNodeById($fileId);
303+
if ($node === null) {
304+
$node = $this->rootFolder->getFirstNodeByIdInPath($fileId, '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
305+
if (!$node instanceof File) {
306+
throw new NotFoundException('Node is not a file');
307+
}
308+
} elseif (!$node instanceof File) {
309+
throw new NotFoundException('Node is not a file');
310+
}
311+
return new Http\DataDownloadResponse($node->getContent(), $node->getName(), $node->getMimeType());
312+
}
313+
271314
/**
272315
* @param Task $task
273316
* @return list<int>
274-
* @throws \OCP\TaskProcessing\Exception\NotFoundException
317+
* @throws NotFoundException
275318
*/
276319
private function extractFileIdsFromTask(Task $task): array {
277320
$ids = [];
278321
$taskTypes = $this->taskProcessingManager->getAvailableTaskTypes();
279322
if (!isset($taskTypes[$task->getTaskTypeId()])) {
280-
throw new \OCP\TaskProcessing\Exception\NotFoundException('Could not find task type');
323+
throw new NotFoundException('Could not find task type');
281324
}
282325
$taskType = $taskTypes[$task->getTaskTypeId()];
283326
foreach ($taskType['inputShape'] + $taskType['optionalInputShape'] as $key => $descriptor) {
@@ -317,22 +360,22 @@ private function extractFileIdsFromTask(Task $task): array {
317360
* 200: Progress updated successfully
318361
* 404: Task not found
319362
*/
320-
#[NoAdminRequired]
321-
#[ApiRoute(verb: 'POST', url: '/tasks/{taskId}/progress', root: '/taskprocessing')]
363+
#[ExAppRequired]
364+
#[ApiRoute(verb: 'POST', url: '/tasks_provider/{taskId}/progress', root: '/taskprocessing')]
322365
public function setProgress(int $taskId, float $progress): DataResponse {
323366
try {
324367
$this->taskProcessingManager->setTaskProgress($taskId, $progress);
325-
$task = $this->taskProcessingManager->getUserTask($taskId, $this->userId);
368+
$task = $this->taskProcessingManager->getTask($taskId);
326369

327370
/** @var CoreTaskProcessingTask $json */
328371
$json = $task->jsonSerialize();
329372

330373
return new DataResponse([
331374
'task' => $json,
332375
]);
333-
} catch (\OCP\TaskProcessing\Exception\NotFoundException $e) {
376+
} catch (NotFoundException) {
334377
return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
335-
} catch (Exception $e) {
378+
} catch (Exception) {
336379
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
337380
}
338381
}
@@ -348,25 +391,23 @@ public function setProgress(int $taskId, float $progress): DataResponse {
348391
* 200: Result updated successfully
349392
* 404: Task not found
350393
*/
351-
#[NoAdminRequired]
352-
#[ApiRoute(verb: 'POST', url: '/tasks/{taskId}/result', root: '/taskprocessing')]
394+
#[ExAppRequired]
395+
#[ApiRoute(verb: 'POST', url: '/tasks_provider/{taskId}/result', root: '/taskprocessing')]
353396
public function setResult(int $taskId, ?array $output = null, ?string $errorMessage = null): DataResponse {
354397
try {
355-
// Check if the current user can access the task
356-
$this->taskProcessingManager->getUserTask($taskId, $this->userId);
357398
// set result
358399
$this->taskProcessingManager->setTaskResult($taskId, $errorMessage, $output);
359-
$task = $this->taskProcessingManager->getUserTask($taskId, $this->userId);
400+
$task = $this->taskProcessingManager->getTask($taskId);
360401

361402
/** @var CoreTaskProcessingTask $json */
362403
$json = $task->jsonSerialize();
363404

364405
return new DataResponse([
365406
'task' => $json,
366407
]);
367-
} catch (\OCP\TaskProcessing\Exception\NotFoundException $e) {
408+
} catch (NotFoundException) {
368409
return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
369-
} catch (Exception $e) {
410+
} catch (Exception) {
370411
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
371412
}
372413
}
@@ -396,9 +437,59 @@ public function cancelTask(int $taskId): DataResponse {
396437
return new DataResponse([
397438
'task' => $json,
398439
]);
399-
} catch (\OCP\TaskProcessing\Exception\NotFoundException $e) {
440+
} catch (NotFoundException) {
400441
return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
401-
} catch (Exception $e) {
442+
} catch (Exception) {
443+
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
444+
}
445+
}
446+
447+
/**
448+
* Returns the next scheduled task for the taskTypeId
449+
*
450+
* @param list<string> $providerIds The ids of the providers
451+
* @param list<string> $taskTypeIds The ids of the task types
452+
* @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{}>
453+
*
454+
* 200: Task returned
455+
* 204: No task found
456+
*/
457+
#[ExAppRequired]
458+
#[ApiRoute(verb: 'GET', url: '/tasks_provider/next', root: '/taskprocessing')]
459+
public function getNextScheduledTask(array $providerIds, array $taskTypeIds): DataResponse {
460+
try {
461+
// restrict $providerIds to providers that are configured as preferred for the passed task types
462+
$providerIds = array_values(array_intersect(array_unique(array_map(fn($taskTypeId) => $this->taskProcessingManager->getPreferredProvider($taskTypeId)->getId(), $taskTypeIds)), $providerIds));
463+
// restrict $taskTypeIds to task types that can actually be run by one of the now restricted providers
464+
$taskTypeIds = array_values(array_filter($taskTypeIds, fn($taskTypeId) => in_array($this->taskProcessingManager->getPreferredProvider($taskTypeId)->getId(), $providerIds, true)));
465+
if (count($providerIds) === 0 || count($taskTypeIds) === 0) {
466+
throw new NotFoundException();
467+
}
468+
469+
$taskIdsToIgnore = [];
470+
while (true) {
471+
$task = $this->taskProcessingManager->getNextScheduledTask($taskTypeIds, $taskIdsToIgnore);
472+
$provider = $this->taskProcessingManager->getPreferredProvider($task->getTaskTypeId());
473+
if (in_array($provider->getId(), $providerIds, true)) {
474+
if ($this->taskProcessingManager->lockTask($task)) {
475+
break;
476+
}
477+
}
478+
$taskIdsToIgnore[] = (int)$task->getId();
479+
}
480+
481+
/** @var CoreTaskProcessingTask $json */
482+
$json = $task->jsonSerialize();
483+
484+
return new DataResponse([
485+
'task' => $json,
486+
'provider' => [
487+
'name' => $provider->getId(),
488+
],
489+
]);
490+
} catch (NotFoundException) {
491+
return new DataResponse(null, Http::STATUS_NO_CONTENT);
492+
} catch (Exception) {
402493
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
403494
}
404495
}

0 commit comments

Comments
 (0)