Skip to content

Commit 7320322

Browse files
authored
Merge pull request #55735 from nextcloud/feat/taskprocessing-api-next-batch
feat(TaskProcessingApiController): Add new next_batch endpoint
2 parents 84be993 + 81bf9f3 commit 7320322

File tree

7 files changed

+795
-26
lines changed

7 files changed

+795
-26
lines changed

core/Controller/TaskProcessingApiController.php

Lines changed: 88 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -532,29 +532,7 @@ public function cancelTask(int $taskId): DataResponse {
532532
#[ApiRoute(verb: 'GET', url: '/tasks_provider/next', root: '/taskprocessing')]
533533
public function getNextScheduledTask(array $providerIds, array $taskTypeIds): DataResponse {
534534
try {
535-
$providerIdsBasedOnTaskTypesWithNull = array_unique(array_map(function ($taskTypeId) {
536-
try {
537-
return $this->taskProcessingManager->getPreferredProvider($taskTypeId)->getId();
538-
} catch (Exception) {
539-
return null;
540-
}
541-
}, $taskTypeIds));
542-
543-
$providerIdsBasedOnTaskTypes = array_filter($providerIdsBasedOnTaskTypesWithNull, fn ($providerId) => $providerId !== null);
544-
545-
// restrict $providerIds to providers that are configured as preferred for the passed task types
546-
$possibleProviderIds = array_values(array_intersect($providerIdsBasedOnTaskTypes, $providerIds));
547-
548-
// restrict $taskTypeIds to task types that can actually be run by one of the now restricted providers
549-
$possibleTaskTypeIds = array_values(array_filter($taskTypeIds, function ($taskTypeId) use ($possibleProviderIds) {
550-
try {
551-
$providerForTaskType = $this->taskProcessingManager->getPreferredProvider($taskTypeId)->getId();
552-
} catch (Exception) {
553-
// no provider found for task type
554-
return false;
555-
}
556-
return in_array($providerForTaskType, $possibleProviderIds, true);
557-
}));
535+
[$possibleProviderIds, $possibleTaskTypeIds] = $this->intersectTaskTypesAndProviders($taskTypeIds, $providerIds);
558536

559537
if (count($possibleProviderIds) === 0 || count($possibleTaskTypeIds) === 0) {
560538
throw new NotFoundException();
@@ -596,6 +574,61 @@ public function getNextScheduledTask(array $providerIds, array $taskTypeIds): Da
596574
}
597575
}
598576

577+
/**
578+
* Returns the next n scheduled tasks for the specified set of taskTypes and providers
579+
* The returned tasks are capped at ~50MiB
580+
*
581+
* @param list<string> $providerIds The ids of the providers
582+
* @param list<string> $taskTypeIds The ids of the task types
583+
* @param int $numberOfTasks The number of tasks to return
584+
* @return DataResponse<Http::STATUS_OK, array{tasks: list<array{task: CoreTaskProcessingTask, provider: string}>, has_more: bool}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
585+
*
586+
* 200: Tasks returned
587+
*/
588+
#[ExAppRequired]
589+
#[ApiRoute(verb: 'GET', url: '/tasks_provider/next_batch', root: '/taskprocessing')]
590+
public function getNextScheduledTaskBatch(array $providerIds, array $taskTypeIds, int $numberOfTasks = 1): DataResponse {
591+
try {
592+
[$possibleProviderIds, $possibleTaskTypeIds] = $this->intersectTaskTypesAndProviders($taskTypeIds, $providerIds);
593+
594+
if (count($possibleProviderIds) === 0 || count($possibleTaskTypeIds) === 0) {
595+
return new DataResponse([
596+
'tasks' => [],
597+
'has_more' => false,
598+
]);
599+
}
600+
601+
$tasks = $this->taskProcessingManager->getNextScheduledTasks($possibleTaskTypeIds, numberOfTasks: $numberOfTasks + 1);
602+
$tasksJson = [];
603+
// Stop when $numberOfTasks is reached or the json payload is larger than 50MiB
604+
while (count($tasks) > 0 && count($tasksJson) < $numberOfTasks && strlen(json_encode($tasks)) < 50 * 1024 * 1024) {
605+
// Until we find a task whose task type is set to be provided by the providers requested with this request
606+
// Or no scheduled task is found anymore (given the taskIds to ignore)
607+
$task = array_shift($tasks);
608+
try {
609+
$provider = $this->taskProcessingManager->getPreferredProvider($task->getTaskTypeId());
610+
if (in_array($provider->getId(), $possibleProviderIds, true)) {
611+
if ($this->taskProcessingManager->lockTask($task)) {
612+
$tasksJson[] = ['task' => $task->jsonSerialize(), 'provider' => $provider->getId()];
613+
continue;
614+
}
615+
}
616+
} catch (Exception) {
617+
// There is no provider set for the task type of this task
618+
// proceed to ignore this task
619+
}
620+
}
621+
$hasMore = count($tasks) > 0;
622+
623+
return new DataResponse([
624+
'tasks' => $tasksJson,
625+
'has_more' => $hasMore,
626+
]);
627+
} catch (Exception) {
628+
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
629+
}
630+
}
631+
599632
/**
600633
* @param resource $data
601634
* @return int
@@ -611,4 +644,36 @@ private function setFileContentsInternal($data): int {
611644
$file = $folder->newFile(time() . '-' . rand(1, 100000), $data);
612645
return $file->getId();
613646
}
647+
648+
/**
649+
* @param array $taskTypeIds
650+
* @param array $providerIds
651+
* @return array
652+
*/
653+
private function intersectTaskTypesAndProviders(array $taskTypeIds, array $providerIds): array {
654+
$providerIdsBasedOnTaskTypesWithNull = array_unique(array_map(function ($taskTypeId) {
655+
try {
656+
return $this->taskProcessingManager->getPreferredProvider($taskTypeId)->getId();
657+
} catch (Exception) {
658+
return null;
659+
}
660+
}, $taskTypeIds));
661+
662+
$providerIdsBasedOnTaskTypes = array_filter($providerIdsBasedOnTaskTypesWithNull, fn ($providerId) => $providerId !== null);
663+
664+
// restrict $providerIds to providers that are configured as preferred for the passed task types
665+
$possibleProviderIds = array_values(array_intersect($providerIdsBasedOnTaskTypes, $providerIds));
666+
667+
// restrict $taskTypeIds to task types that can actually be run by one of the now restricted providers
668+
$possibleTaskTypeIds = array_values(array_filter($taskTypeIds, function ($taskTypeId) use ($possibleProviderIds) {
669+
try {
670+
$providerForTaskType = $this->taskProcessingManager->getPreferredProvider($taskTypeId)->getId();
671+
} catch (Exception) {
672+
// no provider found for task type
673+
return false;
674+
}
675+
return in_array($providerForTaskType, $possibleProviderIds, true);
676+
}));
677+
return [$possibleProviderIds, $possibleTaskTypeIds];
678+
}
614679
}

core/openapi-ex_app.json

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,223 @@
13641364
}
13651365
}
13661366
}
1367+
},
1368+
"/ocs/v2.php/taskprocessing/tasks_provider/next_batch": {
1369+
"get": {
1370+
"operationId": "task_processing_api-get-next-scheduled-task-batch",
1371+
"summary": "Returns the next n scheduled tasks for the specified set of taskTypes and providers The returned tasks are capped at ~50MiB",
1372+
"description": "This endpoint requires admin access",
1373+
"tags": [
1374+
"task_processing_api"
1375+
],
1376+
"security": [
1377+
{
1378+
"bearer_auth": []
1379+
},
1380+
{
1381+
"basic_auth": []
1382+
}
1383+
],
1384+
"parameters": [
1385+
{
1386+
"name": "providerIds[]",
1387+
"in": "query",
1388+
"description": "The ids of the providers",
1389+
"required": true,
1390+
"schema": {
1391+
"type": "array",
1392+
"items": {
1393+
"type": "string"
1394+
}
1395+
}
1396+
},
1397+
{
1398+
"name": "taskTypeIds[]",
1399+
"in": "query",
1400+
"description": "The ids of the task types",
1401+
"required": true,
1402+
"schema": {
1403+
"type": "array",
1404+
"items": {
1405+
"type": "string"
1406+
}
1407+
}
1408+
},
1409+
{
1410+
"name": "numberOfTasks",
1411+
"in": "query",
1412+
"description": "The number of tasks to return",
1413+
"schema": {
1414+
"type": "integer",
1415+
"format": "int64",
1416+
"default": 1
1417+
}
1418+
},
1419+
{
1420+
"name": "OCS-APIRequest",
1421+
"in": "header",
1422+
"description": "Required to be true for the API request to pass",
1423+
"required": true,
1424+
"schema": {
1425+
"type": "boolean",
1426+
"default": true
1427+
}
1428+
}
1429+
],
1430+
"responses": {
1431+
"200": {
1432+
"description": "Tasks returned",
1433+
"content": {
1434+
"application/json": {
1435+
"schema": {
1436+
"type": "object",
1437+
"required": [
1438+
"ocs"
1439+
],
1440+
"properties": {
1441+
"ocs": {
1442+
"type": "object",
1443+
"required": [
1444+
"meta",
1445+
"data"
1446+
],
1447+
"properties": {
1448+
"meta": {
1449+
"$ref": "#/components/schemas/OCSMeta"
1450+
},
1451+
"data": {
1452+
"type": "object",
1453+
"required": [
1454+
"tasks",
1455+
"has_more"
1456+
],
1457+
"properties": {
1458+
"tasks": {
1459+
"type": "array",
1460+
"items": {
1461+
"type": "object",
1462+
"required": [
1463+
"task",
1464+
"provider"
1465+
],
1466+
"properties": {
1467+
"task": {
1468+
"$ref": "#/components/schemas/TaskProcessingTask"
1469+
},
1470+
"provider": {
1471+
"type": "string"
1472+
}
1473+
}
1474+
}
1475+
},
1476+
"has_more": {
1477+
"type": "boolean"
1478+
}
1479+
}
1480+
}
1481+
}
1482+
}
1483+
}
1484+
}
1485+
}
1486+
}
1487+
},
1488+
"500": {
1489+
"description": "",
1490+
"content": {
1491+
"application/json": {
1492+
"schema": {
1493+
"type": "object",
1494+
"required": [
1495+
"ocs"
1496+
],
1497+
"properties": {
1498+
"ocs": {
1499+
"type": "object",
1500+
"required": [
1501+
"meta",
1502+
"data"
1503+
],
1504+
"properties": {
1505+
"meta": {
1506+
"$ref": "#/components/schemas/OCSMeta"
1507+
},
1508+
"data": {
1509+
"type": "object",
1510+
"required": [
1511+
"message"
1512+
],
1513+
"properties": {
1514+
"message": {
1515+
"type": "string"
1516+
}
1517+
}
1518+
}
1519+
}
1520+
}
1521+
}
1522+
}
1523+
}
1524+
}
1525+
},
1526+
"401": {
1527+
"description": "Current user is not logged in",
1528+
"content": {
1529+
"application/json": {
1530+
"schema": {
1531+
"type": "object",
1532+
"required": [
1533+
"ocs"
1534+
],
1535+
"properties": {
1536+
"ocs": {
1537+
"type": "object",
1538+
"required": [
1539+
"meta",
1540+
"data"
1541+
],
1542+
"properties": {
1543+
"meta": {
1544+
"$ref": "#/components/schemas/OCSMeta"
1545+
},
1546+
"data": {}
1547+
}
1548+
}
1549+
}
1550+
}
1551+
}
1552+
}
1553+
},
1554+
"403": {
1555+
"description": "Logged in account must be an admin",
1556+
"content": {
1557+
"application/json": {
1558+
"schema": {
1559+
"type": "object",
1560+
"required": [
1561+
"ocs"
1562+
],
1563+
"properties": {
1564+
"ocs": {
1565+
"type": "object",
1566+
"required": [
1567+
"meta",
1568+
"data"
1569+
],
1570+
"properties": {
1571+
"meta": {
1572+
"$ref": "#/components/schemas/OCSMeta"
1573+
},
1574+
"data": {}
1575+
}
1576+
}
1577+
}
1578+
}
1579+
}
1580+
}
1581+
}
1582+
}
1583+
}
13671584
}
13681585
},
13691586
"tags": [

0 commit comments

Comments
 (0)