Skip to content

Commit 69af641

Browse files
authored
Merge pull request #60500 from nextcloud/enh/noid/taskprocessing-streaming
On behalf of Julien
2 parents 8f93d51 + 76709d7 commit 69af641

18 files changed

Lines changed: 1055 additions & 8 deletions

core/Controller/TaskProcessingApiController.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,37 @@ public function setResult(int $taskId, ?array $output = null, ?string $errorMess
643643
}
644644
}
645645

646+
/**
647+
* Sets the task intermediate result while it is running
648+
*
649+
* @param int $taskId The id of the task
650+
* @param array<string,mixed> $output The intermediate task output, files are represented by their IDs
651+
* @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
652+
*
653+
* 200: Result updated successfully
654+
* 404: Task not found
655+
*/
656+
#[ExAppRequired]
657+
#[ApiRoute(verb: 'POST', url: '/tasks_provider/{taskId}/stream-result', root: '/taskprocessing')]
658+
public function setIntermediateResult(int $taskId, array $output): DataResponse {
659+
try {
660+
// set result
661+
$this->taskProcessingManager->setTaskIntermediateOutput($taskId, $output);
662+
$task = $this->taskProcessingManager->getTask($taskId);
663+
664+
/** @var CoreTaskProcessingTask $json */
665+
$json = $task->jsonSerialize();
666+
667+
return new DataResponse([
668+
'task' => $json,
669+
]);
670+
} catch (NotFoundException) {
671+
return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
672+
} catch (Exception) {
673+
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
674+
}
675+
}
676+
646677
/**
647678
* @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
648679
*/
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OC\Core\Migrations;
11+
12+
use Closure;
13+
use OCP\DB\ISchemaWrapper;
14+
use OCP\DB\Types;
15+
use OCP\Migration\Attributes\AddColumn;
16+
use OCP\Migration\Attributes\ColumnType;
17+
use OCP\Migration\IOutput;
18+
use OCP\Migration\SimpleMigrationStep;
19+
20+
/**
21+
*
22+
*/
23+
#[AddColumn(table: 'taskprocessing_tasks', name: 'prefer_streaming', type: ColumnType::SMALLINT)]
24+
class Version35000Date20260527162338 extends SimpleMigrationStep {
25+
26+
/**
27+
* @param IOutput $output
28+
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
29+
* @param array $options
30+
* @return null|ISchemaWrapper
31+
*/
32+
#[\Override]
33+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
34+
/** @var ISchemaWrapper $schema */
35+
$schema = $schemaClosure();
36+
37+
if ($schema->hasTable('taskprocessing_tasks')) {
38+
$table = $schema->getTable('taskprocessing_tasks');
39+
if (!$table->hasColumn('prefer_streaming')) {
40+
$table->addColumn('prefer_streaming', Types::SMALLINT, [
41+
'notnull' => true,
42+
'default' => 1,
43+
'unsigned' => true,
44+
]);
45+
return $schema;
46+
}
47+
}
48+
49+
return null;
50+
}
51+
}

core/ResponseDefinitions.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@
213213
* allowCleanup: bool,
214214
* includeWatermark: bool,
215215
* userFacingErrorMessage: ?string,
216+
* preferStreaming: bool,
216217
* }
217218
*
218219
* @psalm-type CoreProfileAction = array{

core/openapi-ex_app.json

Lines changed: 239 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@
204204
"endedAt",
205205
"allowCleanup",
206206
"includeWatermark",
207-
"userFacingErrorMessage"
207+
"userFacingErrorMessage",
208+
"preferStreaming"
208209
],
209210
"properties": {
210211
"id": {
@@ -285,6 +286,9 @@
285286
"userFacingErrorMessage": {
286287
"type": "string",
287288
"nullable": true
289+
},
290+
"preferStreaming": {
291+
"type": "boolean"
288292
}
289293
}
290294
},
@@ -2207,6 +2211,240 @@
22072211
}
22082212
}
22092213
},
2214+
"/ocs/v2.php/taskprocessing/tasks_provider/{taskId}/stream-result": {
2215+
"post": {
2216+
"operationId": "task_processing_api-set-intermediate-result",
2217+
"summary": "Sets the task intermediate result while it is running",
2218+
"description": "This endpoint requires admin access",
2219+
"tags": [
2220+
"task_processing_api"
2221+
],
2222+
"security": [
2223+
{
2224+
"bearer_auth": []
2225+
},
2226+
{
2227+
"basic_auth": []
2228+
}
2229+
],
2230+
"requestBody": {
2231+
"required": true,
2232+
"content": {
2233+
"application/json": {
2234+
"schema": {
2235+
"type": "object",
2236+
"required": [
2237+
"output"
2238+
],
2239+
"properties": {
2240+
"output": {
2241+
"type": "object",
2242+
"description": "The intermediate task output, files are represented by their IDs",
2243+
"additionalProperties": {
2244+
"type": "object"
2245+
}
2246+
}
2247+
}
2248+
}
2249+
}
2250+
}
2251+
},
2252+
"parameters": [
2253+
{
2254+
"name": "taskId",
2255+
"in": "path",
2256+
"description": "The id of the task",
2257+
"required": true,
2258+
"schema": {
2259+
"type": "integer",
2260+
"format": "int64"
2261+
}
2262+
},
2263+
{
2264+
"name": "OCS-APIRequest",
2265+
"in": "header",
2266+
"description": "Required to be true for the API request to pass",
2267+
"required": true,
2268+
"schema": {
2269+
"type": "boolean",
2270+
"default": true
2271+
}
2272+
}
2273+
],
2274+
"responses": {
2275+
"200": {
2276+
"description": "Result updated successfully",
2277+
"content": {
2278+
"application/json": {
2279+
"schema": {
2280+
"type": "object",
2281+
"required": [
2282+
"ocs"
2283+
],
2284+
"properties": {
2285+
"ocs": {
2286+
"type": "object",
2287+
"required": [
2288+
"meta",
2289+
"data"
2290+
],
2291+
"properties": {
2292+
"meta": {
2293+
"$ref": "#/components/schemas/OCSMeta"
2294+
},
2295+
"data": {
2296+
"type": "object",
2297+
"required": [
2298+
"task"
2299+
],
2300+
"properties": {
2301+
"task": {
2302+
"$ref": "#/components/schemas/TaskProcessingTask"
2303+
}
2304+
}
2305+
}
2306+
}
2307+
}
2308+
}
2309+
}
2310+
}
2311+
}
2312+
},
2313+
"500": {
2314+
"description": "",
2315+
"content": {
2316+
"application/json": {
2317+
"schema": {
2318+
"type": "object",
2319+
"required": [
2320+
"ocs"
2321+
],
2322+
"properties": {
2323+
"ocs": {
2324+
"type": "object",
2325+
"required": [
2326+
"meta",
2327+
"data"
2328+
],
2329+
"properties": {
2330+
"meta": {
2331+
"$ref": "#/components/schemas/OCSMeta"
2332+
},
2333+
"data": {
2334+
"type": "object",
2335+
"required": [
2336+
"message"
2337+
],
2338+
"properties": {
2339+
"message": {
2340+
"type": "string"
2341+
}
2342+
}
2343+
}
2344+
}
2345+
}
2346+
}
2347+
}
2348+
}
2349+
}
2350+
},
2351+
"404": {
2352+
"description": "Task not found",
2353+
"content": {
2354+
"application/json": {
2355+
"schema": {
2356+
"type": "object",
2357+
"required": [
2358+
"ocs"
2359+
],
2360+
"properties": {
2361+
"ocs": {
2362+
"type": "object",
2363+
"required": [
2364+
"meta",
2365+
"data"
2366+
],
2367+
"properties": {
2368+
"meta": {
2369+
"$ref": "#/components/schemas/OCSMeta"
2370+
},
2371+
"data": {
2372+
"type": "object",
2373+
"required": [
2374+
"message"
2375+
],
2376+
"properties": {
2377+
"message": {
2378+
"type": "string"
2379+
}
2380+
}
2381+
}
2382+
}
2383+
}
2384+
}
2385+
}
2386+
}
2387+
}
2388+
},
2389+
"401": {
2390+
"description": "Current user is not logged in",
2391+
"content": {
2392+
"application/json": {
2393+
"schema": {
2394+
"type": "object",
2395+
"required": [
2396+
"ocs"
2397+
],
2398+
"properties": {
2399+
"ocs": {
2400+
"type": "object",
2401+
"required": [
2402+
"meta",
2403+
"data"
2404+
],
2405+
"properties": {
2406+
"meta": {
2407+
"$ref": "#/components/schemas/OCSMeta"
2408+
},
2409+
"data": {}
2410+
}
2411+
}
2412+
}
2413+
}
2414+
}
2415+
}
2416+
},
2417+
"403": {
2418+
"description": "Logged in account must be an admin",
2419+
"content": {
2420+
"application/json": {
2421+
"schema": {
2422+
"type": "object",
2423+
"required": [
2424+
"ocs"
2425+
],
2426+
"properties": {
2427+
"ocs": {
2428+
"type": "object",
2429+
"required": [
2430+
"meta",
2431+
"data"
2432+
],
2433+
"properties": {
2434+
"meta": {
2435+
"$ref": "#/components/schemas/OCSMeta"
2436+
},
2437+
"data": {}
2438+
}
2439+
}
2440+
}
2441+
}
2442+
}
2443+
}
2444+
}
2445+
}
2446+
}
2447+
},
22102448
"/ocs/v2.php/taskprocessing/tasks_consumer/tasks/{taskId}/cancel": {
22112449
"post": {
22122450
"operationId": "task_processing_api-cancel-task-ex-app-endpoint",

0 commit comments

Comments
 (0)