mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
Merge pull request #45094 from nextcloud/enh/taskprocessing-api
feat: TaskProcessing API
This commit is contained in:
commit
f3e72aff7c
44 changed files with 6434 additions and 22 deletions
430
core/Controller/TaskProcessingApiController.php
Normal file
430
core/Controller/TaskProcessingApiController.php
Normal file
|
|
@ -0,0 +1,430 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace OC\Core\Controller;
|
||||
|
||||
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\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\Common\Exception\NotFoundException;
|
||||
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\UnauthorizedException;
|
||||
use OCP\TaskProcessing\Exception\ValidationException;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
use OCP\TaskProcessing\Task;
|
||||
|
||||
/**
|
||||
* @psalm-import-type CoreTaskProcessingTask from ResponseDefinitions
|
||||
* @psalm-import-type CoreTaskProcessingTaskType from ResponseDefinitions
|
||||
*/
|
||||
class TaskProcessingApiController extends \OCP\AppFramework\OCSController {
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private \OCP\TaskProcessing\IManager $taskProcessingManager,
|
||||
private IL10N $l,
|
||||
private ?string $userId,
|
||||
private IRootFolder $rootFolder,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint returns all available TaskProcessing task types
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, array{types: array<string, CoreTaskProcessingTaskType>}, array{}>
|
||||
*
|
||||
* 200: Task types returned
|
||||
*/
|
||||
#[PublicPage]
|
||||
#[ApiRoute(verb: 'GET', url: '/tasktypes', root: '/taskprocessing')]
|
||||
public function taskTypes(): DataResponse {
|
||||
$taskTypes = $this->taskProcessingManager->getAvailableTaskTypes();
|
||||
|
||||
$serializedTaskTypes = [];
|
||||
foreach ($taskTypes as $key => $taskType) {
|
||||
$serializedTaskTypes[$key] = [
|
||||
'name' => $taskType['name'],
|
||||
'description' => $taskType['description'],
|
||||
'inputShape' => array_map(fn (ShapeDescriptor $descriptor) =>
|
||||
$descriptor->jsonSerialize() + ['mandatory' => true], $taskType['inputShape'])
|
||||
+ array_map(fn (ShapeDescriptor $descriptor) =>
|
||||
$descriptor->jsonSerialize() + ['mandatory' => false], $taskType['optionalInputShape']),
|
||||
'outputShape' => array_map(fn (ShapeDescriptor $descriptor) =>
|
||||
$descriptor->jsonSerialize() + ['mandatory' => true], $taskType['outputShape'])
|
||||
+ array_map(fn (ShapeDescriptor $descriptor) =>
|
||||
$descriptor->jsonSerialize() + ['mandatory' => false], $taskType['optionalOutputShape']),
|
||||
];
|
||||
}
|
||||
|
||||
return new DataResponse([
|
||||
'types' => $serializedTaskTypes,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint allows scheduling a task
|
||||
*
|
||||
* @param array<string, mixed> $input Task's input parameters
|
||||
* @param string $type Type of the task
|
||||
* @param string $appId ID of the app that will execute the task
|
||||
* @param string $customId An arbitrary identifier for the task
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_BAD_REQUEST|Http::STATUS_PRECONDITION_FAILED|Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>
|
||||
*
|
||||
* 200: Task scheduled successfully
|
||||
* 400: Scheduling task is not possible
|
||||
* 412: Scheduling task is not possible
|
||||
* 401: Cannot schedule task because it references files in its input that the user doesn't have access to
|
||||
*/
|
||||
#[PublicPage]
|
||||
#[UserRateLimit(limit: 20, period: 120)]
|
||||
#[AnonRateLimit(limit: 5, period: 120)]
|
||||
#[ApiRoute(verb: 'POST', url: '/schedule', root: '/taskprocessing')]
|
||||
public function schedule(array $input, string $type, string $appId, string $customId = ''): DataResponse {
|
||||
$task = new Task($type, $input, $appId, $this->userId, $customId);
|
||||
try {
|
||||
$this->taskProcessingManager->scheduleTask($task);
|
||||
|
||||
/** @var CoreTaskProcessingTask $json */
|
||||
$json = $task->jsonSerialize();
|
||||
|
||||
return new DataResponse([
|
||||
'task' => $json,
|
||||
]);
|
||||
} catch (\OCP\TaskProcessing\Exception\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) {
|
||||
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) {
|
||||
return new DataResponse(['message' => 'Internal server error'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint allows checking the status and results of a task.
|
||||
* Tasks are removed 1 week after receiving their last update
|
||||
*
|
||||
* @param int $id The id of the task
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
|
||||
*
|
||||
* 200: Task returned
|
||||
* 404: Task not found
|
||||
*/
|
||||
#[PublicPage]
|
||||
#[ApiRoute(verb: 'GET', url: '/task/{id}', root: '/taskprocessing')]
|
||||
public function getTask(int $id): DataResponse {
|
||||
try {
|
||||
$task = $this->taskProcessingManager->getUserTask($id, $this->userId);
|
||||
|
||||
/** @var CoreTaskProcessingTask $json */
|
||||
$json = $task->jsonSerialize();
|
||||
|
||||
return new DataResponse([
|
||||
'task' => $json,
|
||||
]);
|
||||
} catch (NotFoundException $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
|
||||
} catch (\RuntimeException $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint allows to delete a scheduled task for a user
|
||||
*
|
||||
* @param int $id The id of the task
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, null, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
|
||||
*
|
||||
* 200: Task returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[ApiRoute(verb: 'DELETE', url: '/task/{id}', root: '/taskprocessing')]
|
||||
public function deleteTask(int $id): DataResponse {
|
||||
try {
|
||||
$task = $this->taskProcessingManager->getUserTask($id, $this->userId);
|
||||
|
||||
$this->taskProcessingManager->deleteTask($task);
|
||||
|
||||
return new DataResponse(null);
|
||||
} catch (\OCP\TaskProcessing\Exception\NotFoundException $e) {
|
||||
return new DataResponse(null);
|
||||
} catch (\OCP\TaskProcessing\Exception\Exception $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This endpoint returns a list of tasks of a user that are related
|
||||
* with a specific appId and optionally with an identifier
|
||||
*
|
||||
* @param string $appId ID of the app
|
||||
* @param string|null $customId An arbitrary identifier for the task
|
||||
* @return DataResponse<Http::STATUS_OK, array{tasks: CoreTaskProcessingTask[]}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
|
||||
*
|
||||
* 200: Task list returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[ApiRoute(verb: 'GET', url: '/tasks/app/{appId}', root: '/taskprocessing')]
|
||||
public function listTasksByApp(string $appId, ?string $customId = null): DataResponse {
|
||||
try {
|
||||
$tasks = $this->taskProcessingManager->getUserTasksByApp($this->userId, $appId, $customId);
|
||||
/** @var CoreTaskProcessingTask[] $json */
|
||||
$json = array_map(static function (Task $task) {
|
||||
return $task->jsonSerialize();
|
||||
}, $tasks);
|
||||
|
||||
return new DataResponse([
|
||||
'tasks' => $json,
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
} catch (\JsonException $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint returns a list of tasks of a user that are related
|
||||
* with a specific appId and optionally with an identifier
|
||||
*
|
||||
* @param string|null $taskType The task type to filter by
|
||||
* @param string|null $customId An arbitrary identifier for the task
|
||||
* @return DataResponse<Http::STATUS_OK, array{tasks: CoreTaskProcessingTask[]}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
|
||||
*
|
||||
* 200: Task list returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[ApiRoute(verb: 'GET', url: '/tasks', root: '/taskprocessing')]
|
||||
public function listTasksByUser(?string $taskType, ?string $customId = null): DataResponse {
|
||||
try {
|
||||
$tasks = $this->taskProcessingManager->getUserTasks($this->userId, $taskType, $customId);
|
||||
/** @var CoreTaskProcessingTask[] $json */
|
||||
$json = array_map(static function (Task $task) {
|
||||
return $task->jsonSerialize();
|
||||
}, $tasks);
|
||||
|
||||
return new DataResponse([
|
||||
'tasks' => $json,
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
} catch (\JsonException $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint returns the contents of a file referenced in a task
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[Http\Attribute\NoCSRFRequired]
|
||||
#[ApiRoute(verb: 'GET', url: '/tasks/{taskId}/file/{fileId}', root: '/taskprocessing')]
|
||||
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 new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
|
||||
} catch (GenericFileException|NotPermittedException|LockedException|Exception $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Task $task
|
||||
* @return list<int>
|
||||
* @throws \OCP\TaskProcessing\Exception\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');
|
||||
}
|
||||
$taskType = $taskTypes[$task->getTaskTypeId()];
|
||||
foreach ($taskType['inputShape'] + $taskType['optionalInputShape'] as $key => $descriptor) {
|
||||
if (in_array(EShapeType::getScalarType($descriptor->getShapeType()), [EShapeType::File, EShapeType::Image, EShapeType::Audio, EShapeType::Video], true)) {
|
||||
/** @var int|list<int> $inputSlot */
|
||||
$inputSlot = $task->getInput()[$key];
|
||||
if (is_array($inputSlot)) {
|
||||
$ids += $inputSlot;
|
||||
} else {
|
||||
$ids[] = $inputSlot;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($task->getOutput() !== null) {
|
||||
foreach ($taskType['outputShape'] + $taskType['optionalOutputShape'] as $key => $descriptor) {
|
||||
if (in_array(EShapeType::getScalarType($descriptor->getShapeType()), [EShapeType::File, EShapeType::Image, EShapeType::Audio, EShapeType::Video], true)) {
|
||||
/** @var int|list<int> $outputSlot */
|
||||
$outputSlot = $task->getOutput()[$key];
|
||||
if (is_array($outputSlot)) {
|
||||
$ids += $outputSlot;
|
||||
} else {
|
||||
$ids[] = $outputSlot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return array_values($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint sets the task progress
|
||||
*
|
||||
* @param int $taskId The id of the task
|
||||
* @param float $progress The progress
|
||||
* @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
|
||||
*
|
||||
* 200: File content returned
|
||||
* 404: Task not found
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[ApiRoute(verb: 'POST', url: '/tasks/{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);
|
||||
|
||||
/** @var CoreTaskProcessingTask $json */
|
||||
$json = $task->jsonSerialize();
|
||||
|
||||
return new DataResponse([
|
||||
'task' => $json,
|
||||
]);
|
||||
} catch (\OCP\TaskProcessing\Exception\NotFoundException $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
|
||||
} catch (Exception $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint sets the task progress
|
||||
*
|
||||
* @param int $taskId The id of the task
|
||||
* @param array<string,mixed>|null $output The resulting task output
|
||||
* @param string|null $errorMessage An error message if the task failed
|
||||
* @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
|
||||
*
|
||||
* 200: File content returned
|
||||
* 404: Task not found
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[ApiRoute(verb: 'POST', url: '/tasks/{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);
|
||||
|
||||
/** @var CoreTaskProcessingTask $json */
|
||||
$json = $task->jsonSerialize();
|
||||
|
||||
return new DataResponse([
|
||||
'task' => $json,
|
||||
]);
|
||||
} catch (\OCP\TaskProcessing\Exception\NotFoundException $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
|
||||
} catch (Exception $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint cancels a task
|
||||
*
|
||||
* @param int $taskId The id of the task
|
||||
* @return DataResponse<Http::STATUS_OK, array{task: CoreTaskProcessingTask}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
|
||||
*
|
||||
* 200: File content returned
|
||||
* 404: Task not found
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[ApiRoute(verb: 'POST', url: '/tasks/{taskId}/cancel', root: '/taskprocessing')]
|
||||
public function cancelTask(int $taskId): DataResponse {
|
||||
try {
|
||||
// Check if the current user can access the task
|
||||
$this->taskProcessingManager->getUserTask($taskId, $this->userId);
|
||||
// set result
|
||||
$this->taskProcessingManager->cancelTask($taskId);
|
||||
$task = $this->taskProcessingManager->getUserTask($taskId, $this->userId);
|
||||
|
||||
/** @var CoreTaskProcessingTask $json */
|
||||
$json = $task->jsonSerialize();
|
||||
|
||||
return new DataResponse([
|
||||
'task' => $json,
|
||||
]);
|
||||
} catch (\OCP\TaskProcessing\Exception\NotFoundException $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Not found')], Http::STATUS_NOT_FOUND);
|
||||
} catch (Exception $e) {
|
||||
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
115
core/Migrations/Version30000Date20240429122720.php
Normal file
115
core/Migrations/Version30000Date20240429122720.php
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Core\Migrations;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\DB\Types;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class Version30000Date20240429122720 extends SimpleMigrationStep {
|
||||
|
||||
/**
|
||||
* @param IOutput $output
|
||||
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||
* @param array $options
|
||||
* @return null|ISchemaWrapper
|
||||
*/
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
if (!$schema->hasTable('taskprocessing_tasks')) {
|
||||
$table = $schema->createTable('taskprocessing_tasks');
|
||||
|
||||
$table->addColumn('id', Types::BIGINT, [
|
||||
'notnull' => true,
|
||||
'length' => 64,
|
||||
'autoincrement' => true,
|
||||
]);
|
||||
$table->addColumn('type', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 255,
|
||||
]);
|
||||
$table->addColumn('input', Types::TEXT, [
|
||||
'notnull' => true,
|
||||
]);
|
||||
$table->addColumn('output', Types::TEXT, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
$table->addColumn('status', Types::INTEGER, [
|
||||
'notnull' => false,
|
||||
'length' => 6,
|
||||
'default' => 0,
|
||||
]);
|
||||
$table->addColumn('user_id', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 64,
|
||||
]);
|
||||
$table->addColumn('app_id', Types::STRING, [
|
||||
'notnull' => true,
|
||||
'length' => 32,
|
||||
'default' => '',
|
||||
]);
|
||||
$table->addColumn('custom_id', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 255,
|
||||
'default' => '',
|
||||
]);
|
||||
$table->addColumn('last_updated', Types::INTEGER, [
|
||||
'notnull' => false,
|
||||
'length' => 4,
|
||||
'default' => 0,
|
||||
'unsigned' => true,
|
||||
]);
|
||||
$table->addColumn('completion_expected_at', Types::DATETIME, [
|
||||
'notnull' => false,
|
||||
]);
|
||||
$table->addColumn('progress', Types::FLOAT, [
|
||||
'notnull' => false,
|
||||
'default' => 0,
|
||||
]);
|
||||
$table->addColumn('error_message', Types::STRING, [
|
||||
'notnull' => false,
|
||||
'length' => 255,
|
||||
]);
|
||||
|
||||
$table->setPrimaryKey(['id'], 'taskp_tasks_id_index');
|
||||
$table->addIndex(['status', 'type'], 'taskp_tasks_status_type');
|
||||
$table->addIndex(['last_updated'], 'taskp_tasks_updated');
|
||||
$table->addIndex(['user_id', 'app_id', 'custom_id'], 'taskp_tasks_uid_appid_cid');
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -176,6 +176,37 @@ namespace OCA\Core;
|
|||
* iconURL: ?string,
|
||||
* iconEmoji: ?string,
|
||||
* }
|
||||
*
|
||||
* @psalm-type CoreTaskProcessingShape = array{
|
||||
* name: string,
|
||||
* description: string,
|
||||
* type: "Number"|"Text"|"Audio"|"Image"|"Video"|"File"|"ListOfNumbers"|"ListOfTexts"|"ListOfImages"|"ListOfAudios"|"ListOfVideos"|"ListOfFiles",
|
||||
* mandatory: bool,
|
||||
* }
|
||||
*
|
||||
* @psalm-type CoreTaskProcessingTaskType = array{
|
||||
* name: string,
|
||||
* description: string,
|
||||
* inputShape: CoreTaskProcessingShape[],
|
||||
* outputShape: CoreTaskProcessingShape[],
|
||||
* }
|
||||
*
|
||||
* @psalm-type CoreTaskProcessingIO = array<string, numeric|list<numeric>|string|list<string>>
|
||||
*
|
||||
* @psalm-type CoreTaskProcessingTask = array{
|
||||
* id: int,
|
||||
* lastUpdated: int,
|
||||
* type: string,
|
||||
* status: 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN',
|
||||
* userId: ?string,
|
||||
* appId: string,
|
||||
* input: CoreTaskProcessingIO,
|
||||
* output: null|CoreTaskProcessingIO,
|
||||
* customId: ?string,
|
||||
* completionExpectedAt: ?int,
|
||||
* progress: ?float
|
||||
* }
|
||||
*
|
||||
*/
|
||||
class ResponseDefinitions {
|
||||
}
|
||||
|
|
|
|||
1640
core/openapi.json
1640
core/openapi.json
File diff suppressed because it is too large
Load diff
|
|
@ -709,6 +709,30 @@ return array(
|
|||
'OCP\\Talk\\IConversation' => $baseDir . '/lib/public/Talk/IConversation.php',
|
||||
'OCP\\Talk\\IConversationOptions' => $baseDir . '/lib/public/Talk/IConversationOptions.php',
|
||||
'OCP\\Talk\\ITalkBackend' => $baseDir . '/lib/public/Talk/ITalkBackend.php',
|
||||
'OCP\\TaskProcessing\\EShapeType' => $baseDir . '/lib/public/TaskProcessing/EShapeType.php',
|
||||
'OCP\\TaskProcessing\\Events\\AbstractTaskProcessingEvent' => $baseDir . '/lib/public/TaskProcessing/Events/AbstractTaskProcessingEvent.php',
|
||||
'OCP\\TaskProcessing\\Events\\TaskFailedEvent' => $baseDir . '/lib/public/TaskProcessing/Events/TaskFailedEvent.php',
|
||||
'OCP\\TaskProcessing\\Events\\TaskSuccessfulEvent' => $baseDir . '/lib/public/TaskProcessing/Events/TaskSuccessfulEvent.php',
|
||||
'OCP\\TaskProcessing\\Exception\\Exception' => $baseDir . '/lib/public/TaskProcessing/Exception/Exception.php',
|
||||
'OCP\\TaskProcessing\\Exception\\NotFoundException' => $baseDir . '/lib/public/TaskProcessing/Exception/NotFoundException.php',
|
||||
'OCP\\TaskProcessing\\Exception\\PreConditionNotMetException' => $baseDir . '/lib/public/TaskProcessing/Exception/PreConditionNotMetException.php',
|
||||
'OCP\\TaskProcessing\\Exception\\ProcessingException' => $baseDir . '/lib/public/TaskProcessing/Exception/ProcessingException.php',
|
||||
'OCP\\TaskProcessing\\Exception\\UnauthorizedException' => $baseDir . '/lib/public/TaskProcessing/Exception/UnauthorizedException.php',
|
||||
'OCP\\TaskProcessing\\Exception\\ValidationException' => $baseDir . '/lib/public/TaskProcessing/Exception/ValidationException.php',
|
||||
'OCP\\TaskProcessing\\IManager' => $baseDir . '/lib/public/TaskProcessing/IManager.php',
|
||||
'OCP\\TaskProcessing\\IProvider' => $baseDir . '/lib/public/TaskProcessing/IProvider.php',
|
||||
'OCP\\TaskProcessing\\ISynchronousProvider' => $baseDir . '/lib/public/TaskProcessing/ISynchronousProvider.php',
|
||||
'OCP\\TaskProcessing\\ITaskType' => $baseDir . '/lib/public/TaskProcessing/ITaskType.php',
|
||||
'OCP\\TaskProcessing\\ShapeDescriptor' => $baseDir . '/lib/public/TaskProcessing/ShapeDescriptor.php',
|
||||
'OCP\\TaskProcessing\\Task' => $baseDir . '/lib/public/TaskProcessing/Task.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\AudioToText' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/AudioToText.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\ContextWrite' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/ContextWrite.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\GenerateEmoji' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/GenerateEmoji.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\TextToImage' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/TextToImage.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\TextToText' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/TextToText.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\TextToTextHeadline' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/TextToTextHeadline.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\TextToTextSummary' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\TextToTextTopics' => $baseDir . '/lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php',
|
||||
'OCP\\Teams\\ITeamManager' => $baseDir . '/lib/public/Teams/ITeamManager.php',
|
||||
'OCP\\Teams\\ITeamResourceProvider' => $baseDir . '/lib/public/Teams/ITeamResourceProvider.php',
|
||||
'OCP\\Teams\\Team' => $baseDir . '/lib/public/Teams/Team.php',
|
||||
|
|
@ -1185,6 +1209,7 @@ return array(
|
|||
'OC\\Core\\Controller\\ReferenceController' => $baseDir . '/core/Controller/ReferenceController.php',
|
||||
'OC\\Core\\Controller\\SearchController' => $baseDir . '/core/Controller/SearchController.php',
|
||||
'OC\\Core\\Controller\\SetupController' => $baseDir . '/core/Controller/SetupController.php',
|
||||
'OC\\Core\\Controller\\TaskProcessingApiController' => $baseDir . '/core/Controller/TaskProcessingApiController.php',
|
||||
'OC\\Core\\Controller\\TeamsApiController' => $baseDir . '/core/Controller/TeamsApiController.php',
|
||||
'OC\\Core\\Controller\\TextProcessingApiController' => $baseDir . '/core/Controller/TextProcessingApiController.php',
|
||||
'OC\\Core\\Controller\\TextToImageApiController' => $baseDir . '/core/Controller/TextToImageApiController.php',
|
||||
|
|
@ -1278,6 +1303,7 @@ return array(
|
|||
'OC\\Core\\Migrations\\Version29000Date20240124132201' => $baseDir . '/core/Migrations/Version29000Date20240124132201.php',
|
||||
'OC\\Core\\Migrations\\Version29000Date20240124132202' => $baseDir . '/core/Migrations/Version29000Date20240124132202.php',
|
||||
'OC\\Core\\Migrations\\Version29000Date20240131122720' => $baseDir . '/core/Migrations/Version29000Date20240131122720.php',
|
||||
'OC\\Core\\Migrations\\Version30000Date20240429122720' => $baseDir . '/core/Migrations/Version30000Date20240429122720.php',
|
||||
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
|
||||
'OC\\Core\\Service\\LoginFlowV2Service' => $baseDir . '/core/Service/LoginFlowV2Service.php',
|
||||
'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php',
|
||||
|
|
@ -1827,6 +1853,11 @@ return array(
|
|||
'OC\\Tags' => $baseDir . '/lib/private/Tags.php',
|
||||
'OC\\Talk\\Broker' => $baseDir . '/lib/private/Talk/Broker.php',
|
||||
'OC\\Talk\\ConversationOptions' => $baseDir . '/lib/private/Talk/ConversationOptions.php',
|
||||
'OC\\TaskProcessing\\Db\\Task' => $baseDir . '/lib/private/TaskProcessing/Db/Task.php',
|
||||
'OC\\TaskProcessing\\Db\\TaskMapper' => $baseDir . '/lib/private/TaskProcessing/Db/TaskMapper.php',
|
||||
'OC\\TaskProcessing\\Manager' => $baseDir . '/lib/private/TaskProcessing/Manager.php',
|
||||
'OC\\TaskProcessing\\RemoveOldTasksBackgroundJob' => $baseDir . '/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php',
|
||||
'OC\\TaskProcessing\\SynchronousBackgroundJob' => $baseDir . '/lib/private/TaskProcessing/SynchronousBackgroundJob.php',
|
||||
'OC\\Teams\\TeamManager' => $baseDir . '/lib/private/Teams/TeamManager.php',
|
||||
'OC\\TempManager' => $baseDir . '/lib/private/TempManager.php',
|
||||
'OC\\TemplateLayout' => $baseDir . '/lib/private/TemplateLayout.php',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC\\' => 3,
|
||||
'OCP\\' => 4,
|
||||
),
|
||||
'B' =>
|
||||
array (
|
||||
'Bamarni\\Composer\\Bin\\' => 21,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
|
|
@ -32,6 +36,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
array (
|
||||
0 => __DIR__ . '/../../..' . '/lib/public',
|
||||
),
|
||||
'Bamarni\\Composer\\Bin\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/bamarni/composer-bin-plugin/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $fallbackDirsPsr4 = array (
|
||||
|
|
@ -742,6 +750,30 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OCP\\Talk\\IConversation' => __DIR__ . '/../../..' . '/lib/public/Talk/IConversation.php',
|
||||
'OCP\\Talk\\IConversationOptions' => __DIR__ . '/../../..' . '/lib/public/Talk/IConversationOptions.php',
|
||||
'OCP\\Talk\\ITalkBackend' => __DIR__ . '/../../..' . '/lib/public/Talk/ITalkBackend.php',
|
||||
'OCP\\TaskProcessing\\EShapeType' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/EShapeType.php',
|
||||
'OCP\\TaskProcessing\\Events\\AbstractTaskProcessingEvent' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/Events/AbstractTaskProcessingEvent.php',
|
||||
'OCP\\TaskProcessing\\Events\\TaskFailedEvent' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/Events/TaskFailedEvent.php',
|
||||
'OCP\\TaskProcessing\\Events\\TaskSuccessfulEvent' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/Events/TaskSuccessfulEvent.php',
|
||||
'OCP\\TaskProcessing\\Exception\\Exception' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/Exception/Exception.php',
|
||||
'OCP\\TaskProcessing\\Exception\\NotFoundException' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/Exception/NotFoundException.php',
|
||||
'OCP\\TaskProcessing\\Exception\\PreConditionNotMetException' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/Exception/PreConditionNotMetException.php',
|
||||
'OCP\\TaskProcessing\\Exception\\ProcessingException' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/Exception/ProcessingException.php',
|
||||
'OCP\\TaskProcessing\\Exception\\UnauthorizedException' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/Exception/UnauthorizedException.php',
|
||||
'OCP\\TaskProcessing\\Exception\\ValidationException' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/Exception/ValidationException.php',
|
||||
'OCP\\TaskProcessing\\IManager' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/IManager.php',
|
||||
'OCP\\TaskProcessing\\IProvider' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/IProvider.php',
|
||||
'OCP\\TaskProcessing\\ISynchronousProvider' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/ISynchronousProvider.php',
|
||||
'OCP\\TaskProcessing\\ITaskType' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/ITaskType.php',
|
||||
'OCP\\TaskProcessing\\ShapeDescriptor' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/ShapeDescriptor.php',
|
||||
'OCP\\TaskProcessing\\Task' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/Task.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\AudioToText' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/AudioToText.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\ContextWrite' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/ContextWrite.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\GenerateEmoji' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/GenerateEmoji.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\TextToImage' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/TextToImage.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\TextToText' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/TextToText.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\TextToTextHeadline' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/TextToTextHeadline.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\TextToTextSummary' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php',
|
||||
'OCP\\TaskProcessing\\TaskTypes\\TextToTextTopics' => __DIR__ . '/../../..' . '/lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php',
|
||||
'OCP\\Teams\\ITeamManager' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamManager.php',
|
||||
'OCP\\Teams\\ITeamResourceProvider' => __DIR__ . '/../../..' . '/lib/public/Teams/ITeamResourceProvider.php',
|
||||
'OCP\\Teams\\Team' => __DIR__ . '/../../..' . '/lib/public/Teams/Team.php',
|
||||
|
|
@ -1218,6 +1250,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC\\Core\\Controller\\ReferenceController' => __DIR__ . '/../../..' . '/core/Controller/ReferenceController.php',
|
||||
'OC\\Core\\Controller\\SearchController' => __DIR__ . '/../../..' . '/core/Controller/SearchController.php',
|
||||
'OC\\Core\\Controller\\SetupController' => __DIR__ . '/../../..' . '/core/Controller/SetupController.php',
|
||||
'OC\\Core\\Controller\\TaskProcessingApiController' => __DIR__ . '/../../..' . '/core/Controller/TaskProcessingApiController.php',
|
||||
'OC\\Core\\Controller\\TeamsApiController' => __DIR__ . '/../../..' . '/core/Controller/TeamsApiController.php',
|
||||
'OC\\Core\\Controller\\TextProcessingApiController' => __DIR__ . '/../../..' . '/core/Controller/TextProcessingApiController.php',
|
||||
'OC\\Core\\Controller\\TextToImageApiController' => __DIR__ . '/../../..' . '/core/Controller/TextToImageApiController.php',
|
||||
|
|
@ -1311,6 +1344,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC\\Core\\Migrations\\Version29000Date20240124132201' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20240124132201.php',
|
||||
'OC\\Core\\Migrations\\Version29000Date20240124132202' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20240124132202.php',
|
||||
'OC\\Core\\Migrations\\Version29000Date20240131122720' => __DIR__ . '/../../..' . '/core/Migrations/Version29000Date20240131122720.php',
|
||||
'OC\\Core\\Migrations\\Version30000Date20240429122720' => __DIR__ . '/../../..' . '/core/Migrations/Version30000Date20240429122720.php',
|
||||
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
|
||||
'OC\\Core\\Service\\LoginFlowV2Service' => __DIR__ . '/../../..' . '/core/Service/LoginFlowV2Service.php',
|
||||
'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php',
|
||||
|
|
@ -1860,6 +1894,11 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC\\Tags' => __DIR__ . '/../../..' . '/lib/private/Tags.php',
|
||||
'OC\\Talk\\Broker' => __DIR__ . '/../../..' . '/lib/private/Talk/Broker.php',
|
||||
'OC\\Talk\\ConversationOptions' => __DIR__ . '/../../..' . '/lib/private/Talk/ConversationOptions.php',
|
||||
'OC\\TaskProcessing\\Db\\Task' => __DIR__ . '/../../..' . '/lib/private/TaskProcessing/Db/Task.php',
|
||||
'OC\\TaskProcessing\\Db\\TaskMapper' => __DIR__ . '/../../..' . '/lib/private/TaskProcessing/Db/TaskMapper.php',
|
||||
'OC\\TaskProcessing\\Manager' => __DIR__ . '/../../..' . '/lib/private/TaskProcessing/Manager.php',
|
||||
'OC\\TaskProcessing\\RemoveOldTasksBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php',
|
||||
'OC\\TaskProcessing\\SynchronousBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/TaskProcessing/SynchronousBackgroundJob.php',
|
||||
'OC\\Teams\\TeamManager' => __DIR__ . '/../../..' . '/lib/private/Teams/TeamManager.php',
|
||||
'OC\\TempManager' => __DIR__ . '/../../..' . '/lib/private/TempManager.php',
|
||||
'OC\\TemplateLayout' => __DIR__ . '/../../..' . '/lib/private/TemplateLayout.php',
|
||||
|
|
|
|||
|
|
@ -163,6 +163,12 @@ class RegistrationContext {
|
|||
/** @var ServiceRegistration<ITeamResourceProvider>[] */
|
||||
private array $teamResourceProviders = [];
|
||||
|
||||
/** @var ServiceRegistration<\OCP\TaskProcessing\IProvider>[] */
|
||||
private array $taskProcessingProviders = [];
|
||||
|
||||
/** @var ServiceRegistration<\OCP\TaskProcessing\ITaskType>[] */
|
||||
private array $taskProcessingTaskTypes = [];
|
||||
|
||||
public function __construct(LoggerInterface $logger) {
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
|
@ -411,6 +417,20 @@ class RegistrationContext {
|
|||
$declarativeSettingsClass
|
||||
);
|
||||
}
|
||||
|
||||
public function registerTaskProcessingProvider(string $taskProcessingProviderClass): void {
|
||||
$this->context->registerTaskProcessingProvider(
|
||||
$this->appId,
|
||||
$taskProcessingProviderClass
|
||||
);
|
||||
}
|
||||
|
||||
public function registerTaskProcessingTaskType(string $taskProcessingTaskTypeClass): void {
|
||||
$this->context->registerTaskProcessingTaskType(
|
||||
$this->appId,
|
||||
$taskProcessingTaskTypeClass
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -590,6 +610,20 @@ class RegistrationContext {
|
|||
$this->declarativeSettings[] = new ServiceRegistration($appId, $declarativeSettingsClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param class-string<\OCP\TaskProcessing\IProvider> $declarativeSettingsClass
|
||||
*/
|
||||
public function registerTaskProcessingProvider(string $appId, string $taskProcessingProviderClass): void {
|
||||
$this->taskProcessingProviders[] = new ServiceRegistration($appId, $taskProcessingProviderClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param class-string<\OCP\TaskProcessing\ITaskType> $declarativeSettingsClass
|
||||
*/
|
||||
public function registerTaskProcessingTaskType(string $appId, string $taskProcessingTaskTypeClass) {
|
||||
$this->taskProcessingTaskTypes[] = new ServiceRegistration($appId, $taskProcessingTaskTypeClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param App[] $apps
|
||||
*/
|
||||
|
|
@ -920,4 +954,18 @@ class RegistrationContext {
|
|||
public function getDeclarativeSettings(): array {
|
||||
return $this->declarativeSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServiceRegistration<\OCP\TaskProcessing\IProvider>[]
|
||||
*/
|
||||
public function getTaskProcessingProviders(): array {
|
||||
return $this->taskProcessingProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServiceRegistration<\OCP\TaskProcessing\ITaskType>[]
|
||||
*/
|
||||
public function getTaskProcessingTaskTypes(): array {
|
||||
return $this->taskProcessingTaskTypes;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ class Folder extends Node implements \OCP\Files\Folder {
|
|||
return current($this->getById($id)) ?: null;
|
||||
}
|
||||
|
||||
protected function getAppDataDirectoryName(): string {
|
||||
public function getAppDataDirectoryName(): string {
|
||||
$instanceId = \OC::$server->getConfig()->getSystemValueString('instanceid');
|
||||
return 'appdata_' . $instanceId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,4 +64,8 @@ class LazyRoot extends LazyFolder implements IRootFolder {
|
|||
public function getNodeFromCacheEntryAndMount(ICacheEntry $cacheEntry, IMountPoint $mountPoint): INode {
|
||||
return $this->getRootFolder()->getNodeFromCacheEntryAndMount($cacheEntry, $mountPoint);
|
||||
}
|
||||
|
||||
public function getAppDataDirectoryName(): string {
|
||||
return $this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ use OCP\ILogger;
|
|||
use OCP\Log\IDataLogger;
|
||||
use Psr\Log\InvalidArgumentException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Stringable;
|
||||
use Throwable;
|
||||
use function array_key_exists;
|
||||
use function array_merge;
|
||||
|
|
@ -53,10 +52,10 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
|
|||
/**
|
||||
* System is unusable.
|
||||
*
|
||||
* @param string|Stringable $message
|
||||
* @param $message
|
||||
* @param mixed[] $context
|
||||
*/
|
||||
public function emergency(string|Stringable $message, array $context = []): void {
|
||||
public function emergency($message, array $context = []): void {
|
||||
if ($this->containsThrowable($context)) {
|
||||
$this->logger->logException($context['exception'], array_merge(
|
||||
[
|
||||
|
|
@ -76,10 +75,10 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
|
|||
* Example: Entire website down, database unavailable, etc. This should
|
||||
* trigger the SMS alerts and wake you up.
|
||||
*
|
||||
* @param string|Stringable $message
|
||||
* @param $message
|
||||
* @param mixed[] $context
|
||||
*/
|
||||
public function alert(string|Stringable $message, array $context = []): void {
|
||||
public function alert($message, array $context = []): void {
|
||||
if ($this->containsThrowable($context)) {
|
||||
$this->logger->logException($context['exception'], array_merge(
|
||||
[
|
||||
|
|
@ -98,10 +97,10 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
|
|||
*
|
||||
* Example: Application component unavailable, unexpected exception.
|
||||
*
|
||||
* @param string|Stringable $message
|
||||
* @param $message
|
||||
* @param mixed[] $context
|
||||
*/
|
||||
public function critical(string|Stringable $message, array $context = []): void {
|
||||
public function critical($message, array $context = []): void {
|
||||
if ($this->containsThrowable($context)) {
|
||||
$this->logger->logException($context['exception'], array_merge(
|
||||
[
|
||||
|
|
@ -119,10 +118,10 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
|
|||
* Runtime errors that do not require immediate action but should typically
|
||||
* be logged and monitored.
|
||||
*
|
||||
* @param string|Stringable $message
|
||||
* @param $message
|
||||
* @param mixed[] $context
|
||||
*/
|
||||
public function error(string|Stringable $message, array $context = []): void {
|
||||
public function error($message, array $context = []): void {
|
||||
if ($this->containsThrowable($context)) {
|
||||
$this->logger->logException($context['exception'], array_merge(
|
||||
[
|
||||
|
|
@ -142,10 +141,10 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
|
|||
* Example: Use of deprecated APIs, poor use of an API, undesirable things
|
||||
* that are not necessarily wrong.
|
||||
*
|
||||
* @param string|Stringable $message
|
||||
* @param $message
|
||||
* @param mixed[] $context
|
||||
*/
|
||||
public function warning(string|Stringable $message, array $context = []): void {
|
||||
public function warning($message, array $context = []): void {
|
||||
if ($this->containsThrowable($context)) {
|
||||
$this->logger->logException($context['exception'], array_merge(
|
||||
[
|
||||
|
|
@ -162,10 +161,10 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
|
|||
/**
|
||||
* Normal but significant events.
|
||||
*
|
||||
* @param string|Stringable $message
|
||||
* @param $message
|
||||
* @param mixed[] $context
|
||||
*/
|
||||
public function notice(string|Stringable $message, array $context = []): void {
|
||||
public function notice($message, array $context = []): void {
|
||||
if ($this->containsThrowable($context)) {
|
||||
$this->logger->logException($context['exception'], array_merge(
|
||||
[
|
||||
|
|
@ -184,10 +183,10 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
|
|||
*
|
||||
* Example: User logs in, SQL logs.
|
||||
*
|
||||
* @param string|Stringable $message
|
||||
* @param $message
|
||||
* @param mixed[] $context
|
||||
*/
|
||||
public function info(string|Stringable $message, array $context = []): void {
|
||||
public function info($message, array $context = []): void {
|
||||
if ($this->containsThrowable($context)) {
|
||||
$this->logger->logException($context['exception'], array_merge(
|
||||
[
|
||||
|
|
@ -204,10 +203,10 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
|
|||
/**
|
||||
* Detailed debug information.
|
||||
*
|
||||
* @param string|Stringable $message
|
||||
* @param $message
|
||||
* @param mixed[] $context
|
||||
*/
|
||||
public function debug(string|Stringable $message, array $context = []): void {
|
||||
public function debug($message, array $context = []): void {
|
||||
if ($this->containsThrowable($context)) {
|
||||
$this->logger->logException($context['exception'], array_merge(
|
||||
[
|
||||
|
|
@ -225,12 +224,12 @@ final class PsrLoggerAdapter implements LoggerInterface, IDataLogger {
|
|||
* Logs with an arbitrary level.
|
||||
*
|
||||
* @param mixed $level
|
||||
* @param string|Stringable $message
|
||||
* @param $message
|
||||
* @param mixed[] $context
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function log($level, string|Stringable $message, array $context = []): void {
|
||||
public function log($level, $message, array $context = []): void {
|
||||
if (!is_int($level) || $level < ILogger::DEBUG || $level > ILogger::FATAL) {
|
||||
throw new InvalidArgumentException('Nextcloud allows only integer log levels');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace OC\Repair;
|
||||
|
||||
use OC\TaskProcessing\RemoveOldTasksBackgroundJob;
|
||||
use OC\TextProcessing\RemoveOldTasksBackgroundJob as RemoveOldTextProcessingTasksBackgroundJob;
|
||||
use OC\TextToImage\RemoveOldTasksBackgroundJob as RemoveOldTextToImageTasksBackgroundJob;
|
||||
use OCP\BackgroundJob\IJobList;
|
||||
|
|
@ -39,11 +40,12 @@ class AddRemoveOldTasksBackgroundJob implements IRepairStep {
|
|||
}
|
||||
|
||||
public function getName(): string {
|
||||
return 'Add AI tasks cleanup job';
|
||||
return 'Add AI tasks cleanup jobs';
|
||||
}
|
||||
|
||||
public function run(IOutput $output) {
|
||||
$this->jobList->add(RemoveOldTextProcessingTasksBackgroundJob::class);
|
||||
$this->jobList->add(RemoveOldTextToImageTasksBackgroundJob::class);
|
||||
$this->jobList->add(RemoveOldTasksBackgroundJob::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1433,6 +1433,8 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
|
||||
$this->registerAlias(IDeclarativeManager::class, DeclarativeManager::class);
|
||||
|
||||
$this->registerAlias(\OCP\TaskProcessing\IManager::class, \OC\TaskProcessing\Manager::class);
|
||||
|
||||
$this->connectDispatcher();
|
||||
}
|
||||
|
||||
|
|
|
|||
135
lib/private/TaskProcessing/Db/Task.php
Normal file
135
lib/private/TaskProcessing/Db/Task.php
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OC\TaskProcessing\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\TaskProcessing\Task as OCPTask;
|
||||
|
||||
/**
|
||||
* @method setType(string $type)
|
||||
* @method string getType()
|
||||
* @method setLastUpdated(int $lastUpdated)
|
||||
* @method int getLastUpdated()
|
||||
* @method setStatus(int $status)
|
||||
* @method int getStatus()
|
||||
* @method setOutput(string $output)
|
||||
* @method string getOutput()
|
||||
* @method setInput(string $input)
|
||||
* @method string getInput()
|
||||
* @method setUserId(?string $userId)
|
||||
* @method string|null getUserId()
|
||||
* @method setAppId(string $type)
|
||||
* @method string getAppId()
|
||||
* @method setCustomId(string $customId)
|
||||
* @method string getCustomId()
|
||||
* @method setCompletionExpectedAt(null|\DateTime $completionExpectedAt)
|
||||
* @method null|\DateTime getCompletionExpectedAt()
|
||||
* @method setErrorMessage(null|string $error)
|
||||
* @method null|string getErrorMessage()
|
||||
* @method setProgress(null|float $progress)
|
||||
* @method null|float getProgress()
|
||||
*/
|
||||
class Task extends Entity {
|
||||
protected $lastUpdated;
|
||||
protected $type;
|
||||
protected $input;
|
||||
protected $output;
|
||||
protected $status;
|
||||
protected $userId;
|
||||
protected $appId;
|
||||
protected $customId;
|
||||
protected $completionExpectedAt;
|
||||
protected $errorMessage;
|
||||
protected $progress;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'custom_id', 'completion_expected_at', 'error_message', 'progress'];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'customId', 'completionExpectedAt', 'errorMessage', 'progress'];
|
||||
|
||||
|
||||
public function __construct() {
|
||||
// add types in constructor
|
||||
$this->addType('id', 'integer');
|
||||
$this->addType('lastUpdated', 'integer');
|
||||
$this->addType('type', 'string');
|
||||
$this->addType('input', 'string');
|
||||
$this->addType('output', 'string');
|
||||
$this->addType('status', 'integer');
|
||||
$this->addType('userId', 'string');
|
||||
$this->addType('appId', 'string');
|
||||
$this->addType('customId', 'string');
|
||||
$this->addType('completionExpectedAt', 'datetime');
|
||||
$this->addType('errorMessage', 'string');
|
||||
$this->addType('progress', 'float');
|
||||
}
|
||||
|
||||
public function toRow(): array {
|
||||
return array_combine(self::$columns, array_map(function ($field) {
|
||||
return $this->{'get'.ucfirst($field)}();
|
||||
}, self::$fields));
|
||||
}
|
||||
|
||||
public static function fromPublicTask(OCPTask $task): self {
|
||||
/** @var Task $taskEntity */
|
||||
$taskEntity = self::fromParams([
|
||||
'id' => $task->getId(),
|
||||
'type' => $task->getTaskTypeId(),
|
||||
'lastUpdated' => time(),
|
||||
'status' => $task->getStatus(),
|
||||
'input' => json_encode($task->getInput(), JSON_THROW_ON_ERROR),
|
||||
'output' => json_encode($task->getOutput(), JSON_THROW_ON_ERROR),
|
||||
'errorMessage' => $task->getErrorMessage(),
|
||||
'userId' => $task->getUserId(),
|
||||
'appId' => $task->getAppId(),
|
||||
'customId' => $task->getCustomId(),
|
||||
'completionExpectedAt' => $task->getCompletionExpectedAt(),
|
||||
'progress' => $task->getProgress(),
|
||||
]);
|
||||
return $taskEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return OCPTask
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function toPublicTask(): OCPTask {
|
||||
$task = new OCPTask($this->getType(), json_decode($this->getInput(), true, 512, JSON_THROW_ON_ERROR), $this->getAppId(), $this->getuserId(), $this->getCustomId());
|
||||
$task->setId($this->getId());
|
||||
$task->setStatus($this->getStatus());
|
||||
$task->setLastUpdated($this->getLastUpdated());
|
||||
$task->setOutput(json_decode($this->getOutput(), true, 512, JSON_THROW_ON_ERROR));
|
||||
$task->setCompletionExpectedAt($this->getCompletionExpectedAt());
|
||||
$task->setErrorMessage($this->getErrorMessage());
|
||||
$task->setProgress($this->getProgress());
|
||||
return $task;
|
||||
}
|
||||
}
|
||||
159
lib/private/TaskProcessing/Db/TaskMapper.php
Normal file
159
lib/private/TaskProcessing/Db/TaskMapper.php
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OC\TaskProcessing\Db;
|
||||
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\DB\Exception;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
/**
|
||||
* @extends QBMapper<Task>
|
||||
*/
|
||||
class TaskMapper extends QBMapper {
|
||||
public function __construct(
|
||||
IDBConnection $db,
|
||||
private ITimeFactory $timeFactory,
|
||||
) {
|
||||
parent::__construct($db, 'taskprocessing_tasks', Task::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return Task
|
||||
* @throws Exception
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function find(int $id): Task {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Task::$columns)
|
||||
->from($this->tableName)
|
||||
->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $taskType
|
||||
* @return Task
|
||||
* @throws DoesNotExistException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function findOldestScheduledByType(?string $taskType): Task {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Task::$columns)
|
||||
->from($this->tableName)
|
||||
->where($qb->expr()->eq('status', $qb->createPositionalParameter(\OCP\TaskProcessing\Task::STATUS_SCHEDULED, IQueryBuilder::PARAM_INT)))
|
||||
->setMaxResults(1)
|
||||
->orderBy('last_updated', 'ASC');
|
||||
if ($taskType !== null) {
|
||||
$qb->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter($taskType)));
|
||||
}
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param string|null $userId
|
||||
* @return Task
|
||||
* @throws DoesNotExistException
|
||||
* @throws Exception
|
||||
* @throws MultipleObjectsReturnedException
|
||||
*/
|
||||
public function findByIdAndUser(int $id, ?string $userId): Task {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Task::$columns)
|
||||
->from($this->tableName)
|
||||
->where($qb->expr()->eq('id', $qb->createPositionalParameter($id)));
|
||||
if ($userId === null) {
|
||||
$qb->andWhere($qb->expr()->isNull('user_id'));
|
||||
} else {
|
||||
$qb->andWhere($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)));
|
||||
}
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $userId
|
||||
* @param string|null $taskType
|
||||
* @param string|null $customId
|
||||
* @return list<Task>
|
||||
* @throws Exception
|
||||
*/
|
||||
public function findByUserAndTaskType(?string $userId, ?string $taskType = null, ?string $customId = null): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Task::$columns)
|
||||
->from($this->tableName)
|
||||
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)));
|
||||
if ($taskType !== null) {
|
||||
$qb->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter($taskType)));
|
||||
}
|
||||
if ($customId !== null) {
|
||||
$qb->andWhere($qb->expr()->eq('custom_id', $qb->createPositionalParameter($customId)));
|
||||
}
|
||||
return array_values($this->findEntities($qb));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @param string $appId
|
||||
* @param string|null $customId
|
||||
* @return list<Task>
|
||||
* @throws Exception
|
||||
*/
|
||||
public function findUserTasksByApp(?string $userId, string $appId, ?string $customId = null): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select(Task::$columns)
|
||||
->from($this->tableName)
|
||||
->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId)))
|
||||
->andWhere($qb->expr()->eq('app_id', $qb->createPositionalParameter($appId)));
|
||||
if ($customId !== null) {
|
||||
$qb->andWhere($qb->expr()->eq('custom_id', $qb->createPositionalParameter($customId)));
|
||||
}
|
||||
return array_values($this->findEntities($qb));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timeout
|
||||
* @return int the number of deleted tasks
|
||||
* @throws Exception
|
||||
*/
|
||||
public function deleteOlderThan(int $timeout): int {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete($this->tableName)
|
||||
->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($this->timeFactory->getDateTime()->getTimestamp() - $timeout)));
|
||||
return $qb->executeStatement();
|
||||
}
|
||||
|
||||
public function update(Entity $entity): Entity {
|
||||
$entity->setLastUpdated($this->timeFactory->now()->getTimestamp());
|
||||
return parent::update($entity);
|
||||
}
|
||||
}
|
||||
882
lib/private/TaskProcessing/Manager.php
Normal file
882
lib/private/TaskProcessing/Manager.php
Normal file
|
|
@ -0,0 +1,882 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OC\TaskProcessing;
|
||||
|
||||
use OC\AppFramework\Bootstrap\Coordinator;
|
||||
use OC\Files\SimpleFS\SimpleFile;
|
||||
use OC\TaskProcessing\Db\TaskMapper;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\BackgroundJob\IJobList;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\AppData\IAppDataFactory;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\GenericFileException;
|
||||
use OCP\Files\IAppData;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Files\SimpleFS\ISimpleFile;
|
||||
use OCP\IL10N;
|
||||
use OCP\IServerContainer;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Lock\LockedException;
|
||||
use OCP\SpeechToText\ISpeechToTextProvider;
|
||||
use OCP\SpeechToText\ISpeechToTextProviderWithId;
|
||||
use OCP\TaskProcessing\EShapeType;
|
||||
use OCP\TaskProcessing\Events\TaskFailedEvent;
|
||||
use OCP\TaskProcessing\Events\TaskSuccessfulEvent;
|
||||
use OCP\TaskProcessing\Exception\NotFoundException;
|
||||
use OCP\TaskProcessing\Exception\ProcessingException;
|
||||
use OCP\TaskProcessing\Exception\UnauthorizedException;
|
||||
use OCP\TaskProcessing\Exception\ValidationException;
|
||||
use OCP\TaskProcessing\IManager;
|
||||
use OCP\TaskProcessing\IProvider;
|
||||
use OCP\TaskProcessing\ISynchronousProvider;
|
||||
use OCP\TaskProcessing\ITaskType;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
use OCP\TaskProcessing\Task;
|
||||
use OCP\TaskProcessing\TaskTypes\AudioToText;
|
||||
use OCP\TaskProcessing\TaskTypes\TextToImage;
|
||||
use OCP\TaskProcessing\TaskTypes\TextToText;
|
||||
use OCP\TaskProcessing\TaskTypes\TextToTextHeadline;
|
||||
use OCP\TaskProcessing\TaskTypes\TextToTextSummary;
|
||||
use OCP\TaskProcessing\TaskTypes\TextToTextTopics;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Manager implements IManager {
|
||||
|
||||
public const LEGACY_PREFIX_TEXTPROCESSING = 'legacy:TextProcessing:';
|
||||
public const LEGACY_PREFIX_TEXTTOIMAGE = 'legacy:TextToImage:';
|
||||
public const LEGACY_PREFIX_SPEECHTOTEXT = 'legacy:SpeechToText:';
|
||||
|
||||
/** @var list<IProvider>|null */
|
||||
private ?array $providers = null;
|
||||
|
||||
/** @var array<string,array{name: string, description: string, inputShape: array<string, ShapeDescriptor>, optionalInputShape: array<string, ShapeDescriptor>, outputShape: array<string, ShapeDescriptor>, optionalOutputShape: array<string, ShapeDescriptor>}>|null */
|
||||
private ?array $availableTaskTypes = null;
|
||||
|
||||
private IAppData $appData;
|
||||
|
||||
public function __construct(
|
||||
private Coordinator $coordinator,
|
||||
private IServerContainer $serverContainer,
|
||||
private LoggerInterface $logger,
|
||||
private TaskMapper $taskMapper,
|
||||
private IJobList $jobList,
|
||||
private IEventDispatcher $dispatcher,
|
||||
IAppDataFactory $appDataFactory,
|
||||
private IRootFolder $rootFolder,
|
||||
private \OCP\TextProcessing\IManager $textProcessingManager,
|
||||
private \OCP\TextToImage\IManager $textToImageManager,
|
||||
private \OCP\SpeechToText\ISpeechToTextManager $speechToTextManager,
|
||||
private \OCP\Share\IManager $shareManager,
|
||||
) {
|
||||
$this->appData = $appDataFactory->get('core');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IProvider[]
|
||||
*/
|
||||
private function _getTextProcessingProviders(): array {
|
||||
$oldProviders = $this->textProcessingManager->getProviders();
|
||||
$newProviders = [];
|
||||
foreach ($oldProviders as $oldProvider) {
|
||||
$provider = new class($oldProvider) implements IProvider, ISynchronousProvider {
|
||||
private \OCP\TextProcessing\IProvider $provider;
|
||||
|
||||
public function __construct(\OCP\TextProcessing\IProvider $provider) {
|
||||
$this->provider = $provider;
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
if ($this->provider instanceof \OCP\TextProcessing\IProviderWithId) {
|
||||
return $this->provider->getId();
|
||||
}
|
||||
return Manager::LEGACY_PREFIX_TEXTPROCESSING . $this->provider::class;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->provider->getName();
|
||||
}
|
||||
|
||||
public function getTaskTypeId(): string {
|
||||
return match ($this->provider->getTaskType()) {
|
||||
\OCP\TextProcessing\FreePromptTaskType::class => TextToText::ID,
|
||||
\OCP\TextProcessing\HeadlineTaskType::class => TextToTextHeadline::ID,
|
||||
\OCP\TextProcessing\TopicsTaskType::class => TextToTextTopics::ID,
|
||||
\OCP\TextProcessing\SummaryTaskType::class => TextToTextSummary::ID,
|
||||
default => Manager::LEGACY_PREFIX_TEXTPROCESSING . $this->provider->getTaskType(),
|
||||
};
|
||||
}
|
||||
|
||||
public function getExpectedRuntime(): int {
|
||||
if ($this->provider instanceof \OCP\TextProcessing\IProviderWithExpectedRuntime) {
|
||||
return $this->provider->getExpectedRuntime();
|
||||
}
|
||||
return 60;
|
||||
}
|
||||
|
||||
public function getOptionalInputShape(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getOptionalOutputShape(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function process(?string $userId, array $input, callable $reportProgress): array {
|
||||
if ($this->provider instanceof \OCP\TextProcessing\IProviderWithUserId) {
|
||||
$this->provider->setUserId($userId);
|
||||
}
|
||||
try {
|
||||
return ['output' => $this->provider->process($input['input'])];
|
||||
} catch(\RuntimeException $e) {
|
||||
throw new ProcessingException($e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
};
|
||||
$newProviders[$provider->getId()] = $provider;
|
||||
}
|
||||
|
||||
return $newProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ITaskType[]
|
||||
*/
|
||||
private function _getTextProcessingTaskTypes(): array {
|
||||
$oldProviders = $this->textProcessingManager->getProviders();
|
||||
$newTaskTypes = [];
|
||||
foreach ($oldProviders as $oldProvider) {
|
||||
// These are already implemented in the TaskProcessing realm
|
||||
if (in_array($oldProvider->getTaskType(), [
|
||||
\OCP\TextProcessing\FreePromptTaskType::class,
|
||||
\OCP\TextProcessing\HeadlineTaskType::class,
|
||||
\OCP\TextProcessing\TopicsTaskType::class,
|
||||
\OCP\TextProcessing\SummaryTaskType::class
|
||||
], true)) {
|
||||
continue;
|
||||
}
|
||||
$taskType = new class($oldProvider->getTaskType()) implements ITaskType {
|
||||
private string $oldTaskTypeClass;
|
||||
private \OCP\TextProcessing\ITaskType $oldTaskType;
|
||||
private IL10N $l;
|
||||
|
||||
public function __construct(string $oldTaskTypeClass) {
|
||||
$this->oldTaskTypeClass = $oldTaskTypeClass;
|
||||
$this->oldTaskType = \OCP\Server::get($oldTaskTypeClass);
|
||||
$this->l = \OCP\Server::get(IFactory::class)->get('core');
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return Manager::LEGACY_PREFIX_TEXTPROCESSING . $this->oldTaskTypeClass;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->oldTaskType->getName();
|
||||
}
|
||||
|
||||
public function getDescription(): string {
|
||||
return $this->oldTaskType->getDescription();
|
||||
}
|
||||
|
||||
public function getInputShape(): array {
|
||||
return ['input' => new ShapeDescriptor($this->l->t('Input text'), $this->l->t('The input text'), EShapeType::Text)];
|
||||
}
|
||||
|
||||
public function getOutputShape(): array {
|
||||
return ['output' => new ShapeDescriptor($this->l->t('Input text'), $this->l->t('The input text'), EShapeType::Text)];
|
||||
}
|
||||
};
|
||||
$newTaskTypes[$taskType->getId()] = $taskType;
|
||||
}
|
||||
|
||||
return $newTaskTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IProvider[]
|
||||
*/
|
||||
private function _getTextToImageProviders(): array {
|
||||
$oldProviders = $this->textToImageManager->getProviders();
|
||||
$newProviders = [];
|
||||
foreach ($oldProviders as $oldProvider) {
|
||||
$newProvider = new class($oldProvider, $this->appData) implements IProvider, ISynchronousProvider {
|
||||
private \OCP\TextToImage\IProvider $provider;
|
||||
private IAppData $appData;
|
||||
|
||||
public function __construct(\OCP\TextToImage\IProvider $provider, IAppData $appData) {
|
||||
$this->provider = $provider;
|
||||
$this->appData = $appData;
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return Manager::LEGACY_PREFIX_TEXTTOIMAGE . $this->provider->getId();
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->provider->getName();
|
||||
}
|
||||
|
||||
public function getTaskTypeId(): string {
|
||||
return TextToImage::ID;
|
||||
}
|
||||
|
||||
public function getExpectedRuntime(): int {
|
||||
return $this->provider->getExpectedRuntime();
|
||||
}
|
||||
|
||||
public function getOptionalInputShape(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getOptionalOutputShape(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function process(?string $userId, array $input, callable $reportProgress): array {
|
||||
try {
|
||||
$folder = $this->appData->getFolder('text2image');
|
||||
} catch(\OCP\Files\NotFoundException) {
|
||||
$folder = $this->appData->newFolder('text2image');
|
||||
}
|
||||
$resources = [];
|
||||
$files = [];
|
||||
for ($i = 0; $i < $input['numberOfImages']; $i++) {
|
||||
$file = $folder->newFile(time() . '-' . rand(1, 100000) . '-' . $i);
|
||||
$files[] = $file;
|
||||
$resource = $file->write();
|
||||
if ($resource !== false && $resource !== true && is_resource($resource)) {
|
||||
$resources[] = $resource;
|
||||
} else {
|
||||
throw new ProcessingException('Text2Image generation using provider "' . $this->getName() . '" failed: Couldn\'t open file to write.');
|
||||
}
|
||||
}
|
||||
if ($this->provider instanceof \OCP\TextToImage\IProviderWithUserId) {
|
||||
$this->provider->setUserId($userId);
|
||||
}
|
||||
try {
|
||||
$this->provider->generate($input['input'], $resources);
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new ProcessingException($e->getMessage(), 0, $e);
|
||||
}
|
||||
for ($i = 0; $i < $input['numberOfImages']; $i++) {
|
||||
if (is_resource($resources[$i])) {
|
||||
// If $resource hasn't been closed yet, we'll do that here
|
||||
fclose($resources[$i]);
|
||||
}
|
||||
}
|
||||
return ['images' => array_map(fn (ISimpleFile $file) => $file->getContent(), $files)];
|
||||
}
|
||||
};
|
||||
$newProviders[$newProvider->getId()] = $newProvider;
|
||||
}
|
||||
|
||||
return $newProviders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return IProvider[]
|
||||
*/
|
||||
private function _getSpeechToTextProviders(): array {
|
||||
$oldProviders = $this->speechToTextManager->getProviders();
|
||||
$newProviders = [];
|
||||
foreach ($oldProviders as $oldProvider) {
|
||||
$newProvider = new class($oldProvider, $this->rootFolder, $this->appData) implements IProvider, ISynchronousProvider {
|
||||
private ISpeechToTextProvider $provider;
|
||||
private IAppData $appData;
|
||||
|
||||
private IRootFolder $rootFolder;
|
||||
|
||||
public function __construct(ISpeechToTextProvider $provider, IRootFolder $rootFolder, IAppData $appData) {
|
||||
$this->provider = $provider;
|
||||
$this->rootFolder = $rootFolder;
|
||||
$this->appData = $appData;
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
if ($this->provider instanceof ISpeechToTextProviderWithId) {
|
||||
return Manager::LEGACY_PREFIX_SPEECHTOTEXT . $this->provider->getId();
|
||||
}
|
||||
return Manager::LEGACY_PREFIX_SPEECHTOTEXT . $this->provider::class;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->provider->getName();
|
||||
}
|
||||
|
||||
public function getTaskTypeId(): string {
|
||||
return AudioToText::ID;
|
||||
}
|
||||
|
||||
public function getExpectedRuntime(): int {
|
||||
return 60;
|
||||
}
|
||||
|
||||
public function getOptionalInputShape(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getOptionalOutputShape(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function process(?string $userId, array $input, callable $reportProgress): array {
|
||||
try {
|
||||
$result = $this->provider->transcribeFile($input['input']);
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new ProcessingException($e->getMessage(), 0, $e);
|
||||
}
|
||||
return ['output' => $result];
|
||||
}
|
||||
};
|
||||
$newProviders[$newProvider->getId()] = $newProvider;
|
||||
}
|
||||
|
||||
return $newProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IProvider[]
|
||||
*/
|
||||
private function _getProviders(): array {
|
||||
$context = $this->coordinator->getRegistrationContext();
|
||||
|
||||
if ($context === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$providers = [];
|
||||
|
||||
foreach ($context->getTaskProcessingProviders() as $providerServiceRegistration) {
|
||||
$class = $providerServiceRegistration->getService();
|
||||
try {
|
||||
/** @var IProvider $provider */
|
||||
$provider = $this->serverContainer->get($class);
|
||||
if (isset($providers[$provider->getId()])) {
|
||||
$this->logger->warning('Task processing provider ' . $class . ' is using ID ' . $provider->getId() . ' which is already used by ' . $providers[$provider->getId()]::class);
|
||||
}
|
||||
$providers[$provider->getId()] = $provider;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Failed to load task processing provider ' . $class, [
|
||||
'exception' => $e,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$providers += $this->_getTextProcessingProviders() + $this->_getTextToImageProviders() + $this->_getSpeechToTextProviders();
|
||||
|
||||
return $providers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ITaskType[]
|
||||
*/
|
||||
private function _getTaskTypes(): array {
|
||||
$context = $this->coordinator->getRegistrationContext();
|
||||
|
||||
if ($context === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Default task types
|
||||
$taskTypes = [
|
||||
\OCP\TaskProcessing\TaskTypes\TextToText::ID => \OCP\Server::get(\OCP\TaskProcessing\TaskTypes\TextToText::class),
|
||||
\OCP\TaskProcessing\TaskTypes\TextToTextTopics::ID => \OCP\Server::get(\OCP\TaskProcessing\TaskTypes\TextToTextTopics::class),
|
||||
\OCP\TaskProcessing\TaskTypes\TextToTextHeadline::ID => \OCP\Server::get(\OCP\TaskProcessing\TaskTypes\TextToTextHeadline::class),
|
||||
\OCP\TaskProcessing\TaskTypes\TextToTextSummary::ID => \OCP\Server::get(\OCP\TaskProcessing\TaskTypes\TextToTextSummary::class),
|
||||
\OCP\TaskProcessing\TaskTypes\TextToImage::ID => \OCP\Server::get(\OCP\TaskProcessing\TaskTypes\TextToImage::class),
|
||||
\OCP\TaskProcessing\TaskTypes\AudioToText::ID => \OCP\Server::get(\OCP\TaskProcessing\TaskTypes\AudioToText::class),
|
||||
];
|
||||
|
||||
foreach ($context->getTaskProcessingTaskTypes() as $providerServiceRegistration) {
|
||||
$class = $providerServiceRegistration->getService();
|
||||
try {
|
||||
/** @var ITaskType $provider */
|
||||
$taskType = $this->serverContainer->get($class);
|
||||
if (isset($taskTypes[$taskType->getId()])) {
|
||||
$this->logger->warning('Task processing task type ' . $class . ' is using ID ' . $taskType->getId() . ' which is already used by ' . $taskTypes[$taskType->getId()]::class);
|
||||
}
|
||||
$taskTypes[$taskType->getId()] = $taskType;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Failed to load task processing task type ' . $class, [
|
||||
'exception' => $e,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$taskTypes += $this->_getTextProcessingTaskTypes();
|
||||
|
||||
return $taskTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $taskType
|
||||
* @return IProvider
|
||||
* @throws \OCP\TaskProcessing\Exception\Exception
|
||||
*/
|
||||
private function _getPreferredProvider(string $taskType) {
|
||||
$providers = $this->getProviders();
|
||||
foreach ($providers as $provider) {
|
||||
if ($provider->getTaskTypeId() === $taskType) {
|
||||
return $provider;
|
||||
}
|
||||
}
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('No matching provider found');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShapeDescriptor[] $spec
|
||||
* @param array $io
|
||||
* @return void
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validateInput(array $spec, array $io, bool $optional = false): void {
|
||||
foreach ($spec as $key => $descriptor) {
|
||||
$type = $descriptor->getShapeType();
|
||||
if (!isset($io[$key])) {
|
||||
if ($optional) {
|
||||
continue;
|
||||
}
|
||||
throw new ValidationException('Missing key: "' . $key . '"');
|
||||
}
|
||||
try {
|
||||
$type->validateInput($io[$key]);
|
||||
} catch (ValidationException $e) {
|
||||
throw new ValidationException('Failed to validate input key "' . $key . '": ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ShapeDescriptor[] $spec
|
||||
* @param array $io
|
||||
* @param bool $optional
|
||||
* @return void
|
||||
* @throws ValidationException
|
||||
*/
|
||||
private function validateOutput(array $spec, array $io, bool $optional = false): void {
|
||||
foreach ($spec as $key => $descriptor) {
|
||||
$type = $descriptor->getShapeType();
|
||||
if (!isset($io[$key])) {
|
||||
if ($optional) {
|
||||
continue;
|
||||
}
|
||||
throw new ValidationException('Missing key: "' . $key . '"');
|
||||
}
|
||||
try {
|
||||
$type->validateOutput($io[$key]);
|
||||
} catch (ValidationException $e) {
|
||||
throw new ValidationException('Failed to validate output key "' . $key . '": ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array-key, T> $array The array to filter
|
||||
* @param ShapeDescriptor[] ...$specs the specs that define which keys to keep
|
||||
* @return array<array-key, T>
|
||||
* @psalm-template T
|
||||
*/
|
||||
private function removeSuperfluousArrayKeys(array $array, ...$specs): array {
|
||||
$keys = array_unique(array_reduce($specs, fn ($carry, $spec) => $carry + array_keys($spec), []));
|
||||
$values = array_map(fn (string $key) => $array[$key], $keys);
|
||||
return array_combine($keys, $values);
|
||||
}
|
||||
|
||||
public function hasProviders(): bool {
|
||||
return count($this->getProviders()) !== 0;
|
||||
}
|
||||
|
||||
public function getProviders(): array {
|
||||
if ($this->providers === null) {
|
||||
$this->providers = $this->_getProviders();
|
||||
}
|
||||
|
||||
return $this->providers;
|
||||
}
|
||||
|
||||
public function getAvailableTaskTypes(): array {
|
||||
if ($this->availableTaskTypes === null) {
|
||||
$taskTypes = $this->_getTaskTypes();
|
||||
$providers = $this->getProviders();
|
||||
|
||||
$availableTaskTypes = [];
|
||||
foreach ($providers as $provider) {
|
||||
if (!isset($taskTypes[$provider->getTaskTypeId()])) {
|
||||
continue;
|
||||
}
|
||||
$taskType = $taskTypes[$provider->getTaskTypeId()];
|
||||
$availableTaskTypes[$provider->getTaskTypeId()] = [
|
||||
'name' => $taskType->getName(),
|
||||
'description' => $taskType->getDescription(),
|
||||
'inputShape' => $taskType->getInputShape(),
|
||||
'optionalInputShape' => $provider->getOptionalInputShape(),
|
||||
'outputShape' => $taskType->getOutputShape(),
|
||||
'optionalOutputShape' => $provider->getOptionalOutputShape(),
|
||||
];
|
||||
}
|
||||
|
||||
$this->availableTaskTypes = $availableTaskTypes;
|
||||
}
|
||||
|
||||
return $this->availableTaskTypes;
|
||||
}
|
||||
|
||||
public function canHandleTask(Task $task): bool {
|
||||
return isset($this->getAvailableTaskTypes()[$task->getTaskTypeId()]);
|
||||
}
|
||||
|
||||
public function scheduleTask(Task $task): void {
|
||||
if (!$this->canHandleTask($task)) {
|
||||
throw new \OCP\TaskProcessing\Exception\PreConditionNotMetException('No task processing provider is installed that can handle this task type: ' . $task->getTaskTypeId());
|
||||
}
|
||||
$taskTypes = $this->getAvailableTaskTypes();
|
||||
$inputShape = $taskTypes[$task->getTaskTypeId()]['inputShape'];
|
||||
$optionalInputShape = $taskTypes[$task->getTaskTypeId()]['optionalInputShape'];
|
||||
// validate input
|
||||
$this->validateInput($inputShape, $task->getInput());
|
||||
$this->validateInput($optionalInputShape, $task->getInput(), true);
|
||||
// authenticate access to mentioned files
|
||||
$ids = [];
|
||||
foreach ($inputShape + $optionalInputShape as $key => $descriptor) {
|
||||
if (in_array(EShapeType::getScalarType($descriptor->getShapeType()), [EShapeType::File, EShapeType::Image, EShapeType::Audio, EShapeType::Video], true)) {
|
||||
/** @var list<int>|int $inputSlot */
|
||||
$inputSlot = $task->getInput()[$key];
|
||||
if (is_array($inputSlot)) {
|
||||
$ids += $inputSlot;
|
||||
} else {
|
||||
$ids[] = $inputSlot;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($ids as $fileId) {
|
||||
$node = $this->rootFolder->getFirstNodeById($fileId);
|
||||
if ($node === null) {
|
||||
$node = $this->rootFolder->getFirstNodeByIdInPath($fileId, '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
|
||||
if ($node === null) {
|
||||
throw new ValidationException('Could not find file ' . $fileId);
|
||||
}
|
||||
}
|
||||
/** @var array{users:array<string,array{node_id:int, node_path: string}>, remote: array<string,array{node_id:int, node_path: string}>, mail: array<string,array{node_id:int, node_path: string}>} $accessList */
|
||||
$accessList = $this->shareManager->getAccessList($node, true, true);
|
||||
$userIds = array_map(fn ($id) => strval($id), array_keys($accessList['users']));
|
||||
if (!in_array($task->getUserId(), $userIds)) {
|
||||
throw new UnauthorizedException('User ' . $task->getUserId() . ' does not have access to file ' . $fileId);
|
||||
}
|
||||
}
|
||||
// remove superfluous keys and set input
|
||||
$task->setInput($this->removeSuperfluousArrayKeys($task->getInput(), $inputShape, $optionalInputShape));
|
||||
$task->setStatus(Task::STATUS_SCHEDULED);
|
||||
$provider = $this->_getPreferredProvider($task->getTaskTypeId());
|
||||
// calculate expected completion time
|
||||
$completionExpectedAt = new \DateTime('now');
|
||||
$completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S'));
|
||||
$task->setCompletionExpectedAt($completionExpectedAt);
|
||||
// create a db entity and insert into db table
|
||||
$taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task);
|
||||
$this->taskMapper->insert($taskEntity);
|
||||
// make sure the scheduler knows the id
|
||||
$task->setId($taskEntity->getId());
|
||||
// schedule synchronous job if the provider is synchronous
|
||||
if ($provider instanceof ISynchronousProvider) {
|
||||
$this->jobList->add(SynchronousBackgroundJob::class, null);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteTask(Task $task): void {
|
||||
$taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task);
|
||||
$this->taskMapper->delete($taskEntity);
|
||||
}
|
||||
|
||||
public function getTask(int $id): Task {
|
||||
try {
|
||||
$taskEntity = $this->taskMapper->find($id);
|
||||
return $taskEntity->toPublicTask();
|
||||
} catch (DoesNotExistException $e) {
|
||||
throw new NotFoundException('Couldn\'t find task with id ' . $id, 0, $e);
|
||||
} catch (MultipleObjectsReturnedException|\OCP\DB\Exception $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem finding the task', 0, $e);
|
||||
} catch (\JsonException $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem parsing JSON after finding the task', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function cancelTask(int $id): void {
|
||||
$task = $this->getTask($id);
|
||||
if ($task->getStatus() !== Task::STATUS_SCHEDULED && $task->getStatus() !== Task::STATUS_RUNNING) {
|
||||
return;
|
||||
}
|
||||
$task->setStatus(Task::STATUS_CANCELLED);
|
||||
$taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task);
|
||||
try {
|
||||
$this->taskMapper->update($taskEntity);
|
||||
} catch (\OCP\DB\Exception $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem finding the task', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function setTaskProgress(int $id, float $progress): bool {
|
||||
// TODO: Not sure if we should rather catch the exceptions of getTask here and fail silently
|
||||
$task = $this->getTask($id);
|
||||
if ($task->getStatus() === Task::STATUS_CANCELLED) {
|
||||
return false;
|
||||
}
|
||||
$task->setStatus(Task::STATUS_RUNNING);
|
||||
$task->setProgress($progress);
|
||||
$taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task);
|
||||
try {
|
||||
$this->taskMapper->update($taskEntity);
|
||||
} catch (\OCP\DB\Exception $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem finding the task', 0, $e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setTaskResult(int $id, ?string $error, ?array $result): void {
|
||||
// TODO: Not sure if we should rather catch the exceptions of getTask here and fail silently
|
||||
$task = $this->getTask($id);
|
||||
if ($task->getStatus() === Task::STATUS_CANCELLED) {
|
||||
$this->logger->info('A TaskProcessing ' . $task->getTaskTypeId() . ' task with id ' . $id . ' finished but was cancelled in the mean time. Moving on without storing result.');
|
||||
return;
|
||||
}
|
||||
if ($error !== null) {
|
||||
$task->setStatus(Task::STATUS_FAILED);
|
||||
$task->setErrorMessage($error);
|
||||
$this->logger->warning('A TaskProcessing ' . $task->getTaskTypeId() . ' task with id ' . $id . ' failed with the following message: ' . $error);
|
||||
} elseif ($result !== null) {
|
||||
$taskTypes = $this->getAvailableTaskTypes();
|
||||
$outputShape = $taskTypes[$task->getTaskTypeId()]['outputShape'];
|
||||
$optionalOutputShape = $taskTypes[$task->getTaskTypeId()]['optionalOutputShape'];
|
||||
try {
|
||||
// validate output
|
||||
$this->validateOutput($outputShape, $result);
|
||||
$this->validateOutput($optionalOutputShape, $result, true);
|
||||
$output = $this->removeSuperfluousArrayKeys($result, $outputShape, $optionalOutputShape);
|
||||
// extract raw data and put it in files, replace it with file ids
|
||||
$output = $this->encapsulateOutputFileData($output, $outputShape, $optionalOutputShape);
|
||||
$task->setOutput($output);
|
||||
$task->setProgress(1);
|
||||
$task->setStatus(Task::STATUS_SUCCESSFUL);
|
||||
} catch (ValidationException $e) {
|
||||
$task->setProgress(1);
|
||||
$task->setStatus(Task::STATUS_FAILED);
|
||||
$error = 'The task was processed successfully but the provider\'s output doesn\'t pass validation against the task type\'s outputShape spec and/or the provider\'s own optionalOutputShape spec';
|
||||
$task->setErrorMessage($error);
|
||||
$this->logger->error($error, ['exception' => $e]);
|
||||
} catch (NotPermittedException $e) {
|
||||
$task->setProgress(1);
|
||||
$task->setStatus(Task::STATUS_FAILED);
|
||||
$error = 'The task was processed successfully but storing the output in a file failed';
|
||||
$task->setErrorMessage($error);
|
||||
$this->logger->error($error, ['exception' => $e]);
|
||||
|
||||
}
|
||||
}
|
||||
$taskEntity = \OC\TaskProcessing\Db\Task::fromPublicTask($task);
|
||||
try {
|
||||
$this->taskMapper->update($taskEntity);
|
||||
} catch (\OCP\DB\Exception $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem finding the task', 0, $e);
|
||||
}
|
||||
if ($task->getStatus() === Task::STATUS_SUCCESSFUL) {
|
||||
$event = new TaskSuccessfulEvent($task);
|
||||
} else {
|
||||
$event = new TaskFailedEvent($task, $error);
|
||||
}
|
||||
$this->dispatcher->dispatchTyped($event);
|
||||
}
|
||||
|
||||
public function getNextScheduledTask(?string $taskTypeId = null): Task {
|
||||
try {
|
||||
$taskEntity = $this->taskMapper->findOldestScheduledByType($taskTypeId);
|
||||
return $taskEntity->toPublicTask();
|
||||
} catch (DoesNotExistException $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\NotFoundException('Could not find the task', 0, $e);
|
||||
} catch (\OCP\DB\Exception $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem finding the task', 0, $e);
|
||||
} catch (\JsonException $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem parsing JSON after finding the task', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes task input or output data and replaces fileIds with base64 data
|
||||
*
|
||||
* @param string|null $userId
|
||||
* @param array<array-key, list<numeric|string>|numeric|string> $input
|
||||
* @param ShapeDescriptor[] ...$specs the specs
|
||||
* @return array<array-key, list<File|numeric|string>|numeric|string|File>
|
||||
* @throws GenericFileException
|
||||
* @throws LockedException
|
||||
* @throws NotPermittedException
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function fillInputFileData(?string $userId, array $input, ...$specs): array {
|
||||
if ($userId !== null) {
|
||||
\OC_Util::setupFS($userId);
|
||||
}
|
||||
$newInputOutput = [];
|
||||
$spec = array_reduce($specs, fn ($carry, $spec) => $carry + $spec, []);
|
||||
foreach($spec as $key => $descriptor) {
|
||||
$type = $descriptor->getShapeType();
|
||||
if (!isset($input[$key])) {
|
||||
continue;
|
||||
}
|
||||
if (!in_array(EShapeType::getScalarType($type), [EShapeType::Image, EShapeType::Audio, EShapeType::Video, EShapeType::File], true)) {
|
||||
$newInputOutput[$key] = $input[$key];
|
||||
continue;
|
||||
}
|
||||
if ($type->value < 10) {
|
||||
$node = $this->rootFolder->getFirstNodeById((int)$input[$key]);
|
||||
if ($node === null) {
|
||||
$node = $this->rootFolder->getFirstNodeByIdInPath((int)$input[$key], '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
|
||||
if (!$node instanceof File) {
|
||||
throw new ValidationException('File id given for key "' . $key . '" is not a file');
|
||||
}
|
||||
} elseif (!$node instanceof File) {
|
||||
throw new ValidationException('File id given for key "' . $key . '" is not a file');
|
||||
}
|
||||
// TODO: Validate if userId has access to this file
|
||||
$newInputOutput[$key] = $node;
|
||||
} else {
|
||||
$newInputOutput[$key] = [];
|
||||
foreach ($input[$key] as $item) {
|
||||
$node = $this->rootFolder->getFirstNodeById((int)$item);
|
||||
if ($node === null) {
|
||||
$node = $this->rootFolder->getFirstNodeByIdInPath((int)$item, '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
|
||||
if (!$node instanceof File) {
|
||||
throw new ValidationException('File id given for key "' . $key . '" is not a file');
|
||||
}
|
||||
} elseif (!$node instanceof File) {
|
||||
throw new ValidationException('File id given for key "' . $key . '" is not a file');
|
||||
}
|
||||
// TODO: Validate if userId has access to this file
|
||||
$newInputOutput[$key][] = $node;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $newInputOutput;
|
||||
}
|
||||
|
||||
public function getUserTask(int $id, ?string $userId): Task {
|
||||
try {
|
||||
$taskEntity = $this->taskMapper->findByIdAndUser($id, $userId);
|
||||
return $taskEntity->toPublicTask();
|
||||
} catch (DoesNotExistException $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\NotFoundException('Could not find the task', 0, $e);
|
||||
} catch (MultipleObjectsReturnedException|\OCP\DB\Exception $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem finding the task', 0, $e);
|
||||
} catch (\JsonException $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem parsing JSON after finding the task', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function getUserTasks(?string $userId, ?string $taskTypeId = null, ?string $customId = null): array {
|
||||
try {
|
||||
$taskEntities = $this->taskMapper->findByUserAndTaskType($userId, $taskTypeId, $customId);
|
||||
return array_map(fn ($taskEntity): Task => $taskEntity->toPublicTask(), $taskEntities);
|
||||
} catch (\OCP\DB\Exception $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem finding the tasks', 0, $e);
|
||||
} catch (\JsonException $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem parsing JSON after finding the tasks', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
public function getUserTasksByApp(?string $userId, string $appId, ?string $customId = null): array {
|
||||
try {
|
||||
$taskEntities = $this->taskMapper->findUserTasksByApp($userId, $appId, $customId);
|
||||
return array_map(fn ($taskEntity): Task => $taskEntity->toPublicTask(), $taskEntities);
|
||||
} catch (\OCP\DB\Exception $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem finding a task', 0, $e);
|
||||
} catch (\JsonException $e) {
|
||||
throw new \OCP\TaskProcessing\Exception\Exception('There was a problem parsing JSON after finding a task', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*Takes task input or output and replaces base64 data with file ids
|
||||
*
|
||||
* @param array $output
|
||||
* @param ShapeDescriptor[] ...$specs the specs that define which keys to keep
|
||||
* @return array
|
||||
* @throws NotPermittedException
|
||||
*/
|
||||
public function encapsulateOutputFileData(array $output, ...$specs): array {
|
||||
$newOutput = [];
|
||||
try {
|
||||
$folder = $this->appData->getFolder('TaskProcessing');
|
||||
} catch (\OCP\Files\NotFoundException) {
|
||||
$folder = $this->appData->newFolder('TaskProcessing');
|
||||
}
|
||||
$spec = array_reduce($specs, fn ($carry, $spec) => $carry + $spec, []);
|
||||
foreach($spec as $key => $descriptor) {
|
||||
$type = $descriptor->getShapeType();
|
||||
if (!isset($output[$key])) {
|
||||
continue;
|
||||
}
|
||||
if (!in_array(EShapeType::getScalarType($type), [EShapeType::Image, EShapeType::Audio, EShapeType::Video, EShapeType::File], true)) {
|
||||
$newOutput[$key] = $output[$key];
|
||||
continue;
|
||||
}
|
||||
if ($type->value < 10) {
|
||||
/** @var SimpleFile $file */
|
||||
$file = $folder->newFile((string) rand(0, 10000000), $output[$key]);
|
||||
$newOutput[$key] = $file->getId(); // polymorphic call to SimpleFile
|
||||
} else {
|
||||
$newOutput = [];
|
||||
foreach ($output[$key] as $item) {
|
||||
/** @var SimpleFile $file */
|
||||
$file = $folder->newFile((string) rand(0, 10000000), $item);
|
||||
$newOutput[$key][] = $file->getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $newOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Task $task
|
||||
* @return array<array-key, list<numeric|string|File>|numeric|string|File>
|
||||
* @throws GenericFileException
|
||||
* @throws LockedException
|
||||
* @throws NotPermittedException
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function prepareInputData(Task $task): array {
|
||||
$taskTypes = $this->getAvailableTaskTypes();
|
||||
$inputShape = $taskTypes[$task->getTaskTypeId()]['inputShape'];
|
||||
$optionalInputShape = $taskTypes[$task->getTaskTypeId()]['optionalInputShape'];
|
||||
$input = $task->getInput();
|
||||
// validate input, again for good measure (should have been validated in scheduleTask)
|
||||
$this->validateInput($inputShape, $input);
|
||||
$this->validateInput($optionalInputShape, $input, true);
|
||||
$input = $this->removeSuperfluousArrayKeys($input, $inputShape, $optionalInputShape);
|
||||
$input = $this->fillInputFileData($task->getUserId(), $input, $inputShape, $optionalInputShape);
|
||||
return $input;
|
||||
}
|
||||
}
|
||||
75
lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php
Normal file
75
lib/private/TaskProcessing/RemoveOldTasksBackgroundJob.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
namespace OC\TaskProcessing;
|
||||
|
||||
use OC\TaskProcessing\Db\TaskMapper;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\TimedJob;
|
||||
use OCP\Files\AppData\IAppDataFactory;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Files\SimpleFS\ISimpleFolder;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class RemoveOldTasksBackgroundJob extends TimedJob {
|
||||
public const MAX_TASK_AGE_SECONDS = 60 * 50 * 24 * 7 * 4; // 4 weeks
|
||||
private \OCP\Files\IAppData $appData;
|
||||
|
||||
public function __construct(
|
||||
ITimeFactory $timeFactory,
|
||||
private TaskMapper $taskMapper,
|
||||
private LoggerInterface $logger,
|
||||
IAppDataFactory $appDataFactory,
|
||||
) {
|
||||
parent::__construct($timeFactory);
|
||||
$this->setInterval(60 * 60 * 24);
|
||||
// can be deferred to maintenance window
|
||||
$this->setTimeSensitivity(TimedJob::TIME_INSENSITIVE);
|
||||
$this->appData = $appDataFactory->get('core');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function run($argument): void {
|
||||
try {
|
||||
$this->taskMapper->deleteOlderThan(self::MAX_TASK_AGE_SECONDS);
|
||||
} catch (\OCP\DB\Exception $e) {
|
||||
$this->logger->warning('Failed to delete stale task processing tasks', ['exception' => $e]);
|
||||
}
|
||||
try {
|
||||
$this->clearFilesOlderThan($this->appData->getFolder('text2image'), self::MAX_TASK_AGE_SECONDS);
|
||||
} catch (NotFoundException $e) {
|
||||
// noop
|
||||
}
|
||||
try {
|
||||
$this->clearFilesOlderThan($this->appData->getFolder('audio2text'), self::MAX_TASK_AGE_SECONDS);
|
||||
} catch (NotFoundException $e) {
|
||||
// noop
|
||||
}
|
||||
try {
|
||||
$this->clearFilesOlderThan($this->appData->getFolder('TaskProcessing'), self::MAX_TASK_AGE_SECONDS);
|
||||
} catch (NotFoundException $e) {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ISimpleFolder $folder
|
||||
* @param int $ageInSeconds
|
||||
* @return void
|
||||
*/
|
||||
private function clearFilesOlderThan(ISimpleFolder $folder, int $ageInSeconds): void {
|
||||
foreach($folder->getDirectoryListing() as $file) {
|
||||
if ($file->getMTime() < time() - $ageInSeconds) {
|
||||
try {
|
||||
$file->delete();
|
||||
} catch (NotPermittedException $e) {
|
||||
$this->logger->warning('Failed to delete a stale task processing file', ['exception' => $e]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
102
lib/private/TaskProcessing/SynchronousBackgroundJob.php
Normal file
102
lib/private/TaskProcessing/SynchronousBackgroundJob.php
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
namespace OC\TaskProcessing;
|
||||
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\IJobList;
|
||||
use OCP\BackgroundJob\QueuedJob;
|
||||
use OCP\Files\GenericFileException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Lock\LockedException;
|
||||
use OCP\TaskProcessing\Exception\Exception;
|
||||
use OCP\TaskProcessing\Exception\NotFoundException;
|
||||
use OCP\TaskProcessing\Exception\ProcessingException;
|
||||
use OCP\TaskProcessing\Exception\ValidationException;
|
||||
use OCP\TaskProcessing\IManager;
|
||||
use OCP\TaskProcessing\ISynchronousProvider;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class SynchronousBackgroundJob extends QueuedJob {
|
||||
public function __construct(
|
||||
ITimeFactory $timeFactory,
|
||||
private readonly IManager $taskProcessingManager,
|
||||
private readonly IJobList $jobList,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($timeFactory);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function run($argument) {
|
||||
$providers = $this->taskProcessingManager->getProviders();
|
||||
|
||||
foreach ($providers as $provider) {
|
||||
if (!$provider instanceof ISynchronousProvider) {
|
||||
continue;
|
||||
}
|
||||
$taskType = $provider->getTaskTypeId();
|
||||
try {
|
||||
$task = $this->taskProcessingManager->getNextScheduledTask($taskType);
|
||||
} catch (NotFoundException $e) {
|
||||
continue;
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Unknown error while retrieving scheduled TaskProcessing tasks', ['exception' => $e]);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
try {
|
||||
$input = $this->taskProcessingManager->prepareInputData($task);
|
||||
} catch (GenericFileException|NotPermittedException|LockedException|ValidationException $e) {
|
||||
$this->logger->warning('Failed to prepare input data for a TaskProcessing task with synchronous provider ' . $provider->getId(), ['exception' => $e]);
|
||||
$this->taskProcessingManager->setTaskResult($task->getId(), $e->getMessage(), null);
|
||||
// Schedule again
|
||||
$this->jobList->add(self::class, $argument);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$output = $provider->process($task->getUserId(), $input, fn (float $progress) => $this->taskProcessingManager->setTaskProgress($task->getId(), $progress));
|
||||
} catch (ProcessingException $e) {
|
||||
$this->logger->warning('Failed to process a TaskProcessing task with synchronous provider ' . $provider->getId(), ['exception' => $e]);
|
||||
$this->taskProcessingManager->setTaskResult($task->getId(), $e->getMessage(), null);
|
||||
// Schedule again
|
||||
$this->jobList->add(self::class, $argument);
|
||||
return;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Unknown error while processing TaskProcessing task', ['exception' => $e]);
|
||||
$this->taskProcessingManager->setTaskResult($task->getId(), $e->getMessage(), null);
|
||||
// Schedule again
|
||||
$this->jobList->add(self::class, $argument);
|
||||
return;
|
||||
}
|
||||
$this->taskProcessingManager->setTaskResult($task->getId(), null, $output);
|
||||
} catch (NotFoundException $e) {
|
||||
$this->logger->info('Could not find task anymore after execution. Moving on.', ['exception' => $e]);
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Failed to report result of TaskProcessing task', ['exception' => $e]);
|
||||
}
|
||||
}
|
||||
|
||||
$synchronousProviders = array_filter($providers, fn ($provider) =>
|
||||
$provider instanceof ISynchronousProvider);
|
||||
$taskTypes = array_values(array_map(fn ($provider) =>
|
||||
$provider->getTaskTypeId(),
|
||||
$synchronousProviders
|
||||
));
|
||||
$taskTypesWithTasks = array_filter($taskTypes, function ($taskType) {
|
||||
try {
|
||||
$this->taskProcessingManager->getNextScheduledTask($taskType);
|
||||
return true;
|
||||
} catch (NotFoundException|Exception $e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (count($taskTypesWithTasks) > 0) {
|
||||
// Schedule again
|
||||
$this->jobList->add(self::class, $argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -98,4 +98,10 @@ interface IRootFolder extends Folder, Emitter {
|
|||
* @since 28.0.0
|
||||
*/
|
||||
public function getMount(string $mountPoint): IMountPoint;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getAppDataDirectoryName(): string;
|
||||
}
|
||||
|
|
|
|||
144
lib/public/TaskProcessing/EShapeType.php
Normal file
144
lib/public/TaskProcessing/EShapeType.php
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCP\TaskProcessing;
|
||||
|
||||
use OCP\TaskProcessing\Exception\ValidationException;
|
||||
|
||||
/**
|
||||
* The input and output Shape types
|
||||
*
|
||||
* @since 30.0.0
|
||||
*/
|
||||
enum EShapeType: int {
|
||||
case Number = 0;
|
||||
case Text = 1;
|
||||
case Image = 2;
|
||||
case Audio = 3;
|
||||
case Video = 4;
|
||||
case File = 5;
|
||||
case ListOfNumbers = 10;
|
||||
case ListOfTexts = 11;
|
||||
case ListOfImages = 12;
|
||||
case ListOfAudios = 13;
|
||||
case ListOfVideos = 14;
|
||||
case ListOfFiles = 15;
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
* @throws ValidationException
|
||||
* @since 30.0.0
|
||||
*/
|
||||
private function validateNonFileType(mixed $value): void {
|
||||
if ($this === EShapeType::Text && !is_string($value)) {
|
||||
throw new ValidationException('Non-text item provided for Text slot');
|
||||
}
|
||||
if ($this === EShapeType::ListOfTexts && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item))) > 0)) {
|
||||
throw new ValidationException('Non-text list item provided for ListOfTexts slot');
|
||||
}
|
||||
if ($this === EShapeType::Number && !is_numeric($value)) {
|
||||
throw new ValidationException('Non-numeric item provided for Number slot');
|
||||
}
|
||||
if ($this === EShapeType::ListOfNumbers && (!is_array($value) || count(array_filter($value, fn ($item) => !is_numeric($item))) > 0)) {
|
||||
throw new ValidationException('Non-numeric list item provided for ListOfNumbers slot');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
* @throws Exception\ValidationException
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function validateInput(mixed $value): void {
|
||||
$this->validateNonFileType($value);
|
||||
if ($this === EShapeType::Image && !is_numeric($value)) {
|
||||
throw new ValidationException('Non-image item provided for Image slot');
|
||||
}
|
||||
if ($this === EShapeType::ListOfImages && (!is_array($value) || count(array_filter($value, fn ($item) => !is_numeric($item))) > 0)) {
|
||||
throw new ValidationException('Non-image list item provided for ListOfImages slot');
|
||||
}
|
||||
if ($this === EShapeType::Audio && !is_numeric($value)) {
|
||||
throw new ValidationException('Non-audio item provided for Audio slot');
|
||||
}
|
||||
if ($this === EShapeType::ListOfAudios && (!is_array($value) || count(array_filter($value, fn ($item) => !is_numeric($item))) > 0)) {
|
||||
throw new ValidationException('Non-audio list item provided for ListOfAudio slot');
|
||||
}
|
||||
if ($this === EShapeType::Video && !is_numeric($value)) {
|
||||
throw new ValidationException('Non-video item provided for Video slot');
|
||||
}
|
||||
if ($this === EShapeType::ListOfVideos && (!is_array($value) || count(array_filter($value, fn ($item) => !is_numeric($item))) > 0)) {
|
||||
throw new ValidationException('Non-video list item provided for ListOfTexts slot');
|
||||
}
|
||||
if ($this === EShapeType::File && !is_numeric($value)) {
|
||||
throw new ValidationException('Non-file item provided for File slot');
|
||||
}
|
||||
if ($this === EShapeType::ListOfFiles && (!is_array($value) || count(array_filter($value, fn ($item) => !is_numeric($item))) > 0)) {
|
||||
throw new ValidationException('Non-audio list item provided for ListOfFiles slot');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ValidationException
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function validateOutput(mixed $value) {
|
||||
$this->validateNonFileType($value);
|
||||
if ($this === EShapeType::Image && !is_string($value)) {
|
||||
throw new ValidationException('Non-image item provided for Image slot');
|
||||
}
|
||||
if ($this === EShapeType::ListOfImages && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item))) > 0)) {
|
||||
throw new ValidationException('Non-image list item provided for ListOfImages slot');
|
||||
}
|
||||
if ($this === EShapeType::Audio && !is_string($value)) {
|
||||
throw new ValidationException('Non-audio item provided for Audio slot');
|
||||
}
|
||||
if ($this === EShapeType::ListOfAudios && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item))) > 0)) {
|
||||
throw new ValidationException('Non-audio list item provided for ListOfAudio slot');
|
||||
}
|
||||
if ($this === EShapeType::Video && !is_string($value)) {
|
||||
throw new ValidationException('Non-video item provided for Video slot');
|
||||
}
|
||||
if ($this === EShapeType::ListOfVideos && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item))) > 0)) {
|
||||
throw new ValidationException('Non-video list item provided for ListOfTexts slot');
|
||||
}
|
||||
if ($this === EShapeType::File && !is_string($value)) {
|
||||
throw new ValidationException('Non-file item provided for File slot');
|
||||
}
|
||||
if ($this === EShapeType::ListOfFiles && (!is_array($value) || count(array_filter($value, fn ($item) => !is_string($item))) > 0)) {
|
||||
throw new ValidationException('Non-audio list item provided for ListOfFiles slot');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EShapeType $type
|
||||
* @return EShapeType
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public static function getScalarType(EShapeType $type): EShapeType {
|
||||
return EShapeType::from($type->value % 10);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCP\TaskProcessing\Events;
|
||||
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\TaskProcessing\Task;
|
||||
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
abstract class AbstractTaskProcessingEvent extends Event {
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly Task $task
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Task
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getTask(): Task {
|
||||
return $this->task;
|
||||
}
|
||||
}
|
||||
30
lib/public/TaskProcessing/Events/TaskFailedEvent.php
Normal file
30
lib/public/TaskProcessing/Events/TaskFailedEvent.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace OCP\TaskProcessing\Events;
|
||||
|
||||
use OCP\TaskProcessing\Task;
|
||||
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class TaskFailedEvent extends AbstractTaskProcessingEvent {
|
||||
/**
|
||||
* @param Task $task
|
||||
* @param string $errorMessage
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
Task $task,
|
||||
private readonly string $errorMessage,
|
||||
) {
|
||||
parent::__construct($task);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getErrorMessage(): string {
|
||||
return $this->errorMessage;
|
||||
}
|
||||
}
|
||||
9
lib/public/TaskProcessing/Events/TaskSuccessfulEvent.php
Normal file
9
lib/public/TaskProcessing/Events/TaskSuccessfulEvent.php
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace OCP\TaskProcessing\Events;
|
||||
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class TaskSuccessfulEvent extends AbstractTaskProcessingEvent {
|
||||
}
|
||||
34
lib/public/TaskProcessing/Exception/Exception.php
Normal file
34
lib/public/TaskProcessing/Exception/Exception.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace OCP\TaskProcessing\Exception;
|
||||
|
||||
/**
|
||||
* TaskProcessing Exception
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class Exception extends \Exception {
|
||||
}
|
||||
10
lib/public/TaskProcessing/Exception/NotFoundException.php
Normal file
10
lib/public/TaskProcessing/Exception/NotFoundException.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace OCP\TaskProcessing\Exception;
|
||||
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class NotFoundException extends Exception {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace OCP\TaskProcessing\Exception;
|
||||
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class PreConditionNotMetException extends Exception {
|
||||
|
||||
}
|
||||
35
lib/public/TaskProcessing/Exception/ProcessingException.php
Normal file
35
lib/public/TaskProcessing/Exception/ProcessingException.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace OCP\TaskProcessing\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown during processing of a task
|
||||
* by a synchronous provider
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class ProcessingException extends \RuntimeException {
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace OCP\TaskProcessing\Exception;
|
||||
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class UnauthorizedException extends Exception {
|
||||
|
||||
}
|
||||
10
lib/public/TaskProcessing/Exception/ValidationException.php
Normal file
10
lib/public/TaskProcessing/Exception/ValidationException.php
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace OCP\TaskProcessing\Exception;
|
||||
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class ValidationException extends Exception {
|
||||
|
||||
}
|
||||
173
lib/public/TaskProcessing/IManager.php
Normal file
173
lib/public/TaskProcessing/IManager.php
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace OCP\TaskProcessing;
|
||||
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\GenericFileException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Lock\LockedException;
|
||||
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;
|
||||
|
||||
/**
|
||||
* API surface for apps interacting with and making use of LanguageModel providers
|
||||
* without known which providers are installed
|
||||
* @since 30.0.0
|
||||
*/
|
||||
interface IManager {
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function hasProviders(): bool;
|
||||
|
||||
/**
|
||||
* @return IProvider[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getProviders(): array;
|
||||
|
||||
/**
|
||||
* @return array<string,array{name: string, description: string, inputShape: ShapeDescriptor[], optionalInputShape: ShapeDescriptor[], outputShape: ShapeDescriptor[], optionalOutputShape: ShapeDescriptor[]}>
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getAvailableTaskTypes(): array;
|
||||
|
||||
/**
|
||||
* @param Task $task The task to run
|
||||
* @throws PreConditionNotMetException If no or not the requested provider was registered but this method was still called
|
||||
* @throws ValidationException the given task input didn't pass validation against the task type's input shape and/or the providers optional input shape specs
|
||||
* @throws Exception storing the task in the database failed
|
||||
* @throws UnauthorizedException the user scheduling the task does not have access to the files used in the input
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function scheduleTask(Task $task): void;
|
||||
|
||||
/**
|
||||
* Delete a task that has been scheduled before
|
||||
*
|
||||
* @param Task $task The task to delete
|
||||
* @throws Exception if deleting the task in the database failed
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function deleteTask(Task $task): void;
|
||||
|
||||
/**
|
||||
* @param int $id The id of the task
|
||||
* @return Task
|
||||
* @throws Exception If the query failed
|
||||
* @throws NotFoundException If the task could not be found
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getTask(int $id): Task;
|
||||
|
||||
/**
|
||||
* @param int $id The id of the task
|
||||
* @throws Exception If the query failed
|
||||
* @throws NotFoundException If the task could not be found
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function cancelTask(int $id): void;
|
||||
|
||||
/**
|
||||
* @param int $id The id of the task
|
||||
* @param string|null $error
|
||||
* @param array|null $result
|
||||
* @throws Exception If the query failed
|
||||
* @throws NotFoundException If the task could not be found
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function setTaskResult(int $id, ?string $error, ?array $result): void;
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param float $progress
|
||||
* @return bool `true` if the task should still be running; `false` if the task has been cancelled in the meantime
|
||||
* @throws ValidationException
|
||||
* @throws Exception
|
||||
* @throws NotFoundException
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function setTaskProgress(int $id, float $progress): bool;
|
||||
|
||||
/**
|
||||
* @param string|null $taskTypeId
|
||||
* @return Task
|
||||
* @throws Exception If the query failed
|
||||
* @throws NotFoundException If no task could not be found
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getNextScheduledTask(?string $taskTypeId = null): Task;
|
||||
|
||||
/**
|
||||
* @param int $id The id of the task
|
||||
* @param string|null $userId The user id that scheduled the task
|
||||
* @return Task
|
||||
* @throws Exception If the query failed
|
||||
* @throws NotFoundException If the task could not be found
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getUserTask(int $id, ?string $userId): Task;
|
||||
|
||||
/**
|
||||
* @param string|null $userId The user id that scheduled the task
|
||||
* @param string|null $taskTypeId The task type id to filter by
|
||||
* @param string|null $customId
|
||||
* @return list<Task>
|
||||
* @throws Exception If the query failed
|
||||
* @throws NotFoundException If the task could not be found
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getUserTasks(?string $userId, ?string $taskTypeId = null, ?string $customId = null): array;
|
||||
|
||||
/**
|
||||
* @param string|null $userId
|
||||
* @param string $appId
|
||||
* @param string|null $customId
|
||||
* @return list<Task>
|
||||
* @throws Exception If the query failed
|
||||
* @throws \JsonException If parsing the task input and output failed
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getUserTasksByApp(?string $userId, string $appId, ?string $customId = null): array;
|
||||
|
||||
/**
|
||||
* Prepare the task's input data, so it can be processed by the provider
|
||||
* ie. this replaces file ids with base64 data
|
||||
*
|
||||
* @param Task $task
|
||||
* @return array<array-key, list<numeric|string|File>|numeric|string|File>
|
||||
* @throws NotPermittedException
|
||||
* @throws GenericFileException
|
||||
* @throws LockedException
|
||||
* @throws ValidationException
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function prepareInputData(Task $task): array;
|
||||
}
|
||||
77
lib/public/TaskProcessing/IProvider.php
Normal file
77
lib/public/TaskProcessing/IProvider.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace OCP\TaskProcessing;
|
||||
|
||||
/**
|
||||
* This is the interface that is implemented by apps that
|
||||
* implement a task processing provider
|
||||
* @since 30.0.0
|
||||
*/
|
||||
interface IProvider {
|
||||
/**
|
||||
* The unique id of this provider
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getId(): string;
|
||||
|
||||
/**
|
||||
* The localized name of this provider
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* Returns the task type id of the task type, that this
|
||||
* provider handles
|
||||
*
|
||||
* @since 30.0.0
|
||||
* @return string
|
||||
*/
|
||||
public function getTaskTypeId(): string;
|
||||
|
||||
/**
|
||||
* @return int The expected average runtime of a task in seconds
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getExpectedRuntime(): int;
|
||||
|
||||
/**
|
||||
* Returns the shape of optional input parameters
|
||||
*
|
||||
* @since 30.0.0
|
||||
* @psalm-return ShapeDescriptor[]
|
||||
*/
|
||||
public function getOptionalInputShape(): array;
|
||||
|
||||
/**
|
||||
* Returns the shape of optional output parameters
|
||||
*
|
||||
* @since 30.0.0
|
||||
* @psalm-return ShapeDescriptor[]
|
||||
*/
|
||||
public function getOptionalOutputShape(): array;
|
||||
}
|
||||
50
lib/public/TaskProcessing/ISynchronousProvider.php
Normal file
50
lib/public/TaskProcessing/ISynchronousProvider.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace OCP\TaskProcessing;
|
||||
|
||||
use OCP\Files\File;
|
||||
use OCP\TaskProcessing\Exception\ProcessingException;
|
||||
|
||||
/**
|
||||
* This is the interface that is implemented by apps that
|
||||
* implement a task processing provider
|
||||
* @since 30.0.0
|
||||
*/
|
||||
interface ISynchronousProvider extends IProvider {
|
||||
|
||||
/**
|
||||
* Returns the shape of optional output parameters
|
||||
*
|
||||
* @param null|string $userId The user that created the current task
|
||||
* @param array<string, list<numeric|string|File>|numeric|string|File> $input The task input
|
||||
* @param callable(float):bool $reportProgress Report the task progress. If this returns false, that means the task was cancelled and processing should be stopped.
|
||||
* @psalm-return array<string, list<numeric|string>|numeric|string>
|
||||
* @throws ProcessingException
|
||||
*@since 30.0.0
|
||||
*/
|
||||
public function process(?string $userId, array $input, callable $reportProgress): array;
|
||||
}
|
||||
73
lib/public/TaskProcessing/ITaskType.php
Normal file
73
lib/public/TaskProcessing/ITaskType.php
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCP\TaskProcessing;
|
||||
|
||||
/**
|
||||
* This is a task type interface that is implemented by task processing
|
||||
* task types
|
||||
* @since 30.0.0
|
||||
*/
|
||||
interface ITaskType {
|
||||
/**
|
||||
* Returns the unique id of this task type
|
||||
*
|
||||
* @since 30.0.0
|
||||
* @return string
|
||||
*/
|
||||
public function getId(): string;
|
||||
|
||||
/**
|
||||
* Returns the localized name of this task type
|
||||
*
|
||||
* @since 30.0.0
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string;
|
||||
|
||||
/**
|
||||
* Returns the localized description of this task type
|
||||
*
|
||||
* @since 30.0.0
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string;
|
||||
|
||||
/**
|
||||
* Returns the shape of the input array
|
||||
*
|
||||
* @since 30.0.0
|
||||
* @psalm-return ShapeDescriptor[]
|
||||
*/
|
||||
public function getInputShape(): array;
|
||||
|
||||
/**
|
||||
* Returns the shape of the output array
|
||||
*
|
||||
* @since 30.0.0
|
||||
* @psalm-return ShapeDescriptor[]
|
||||
*/
|
||||
public function getOutputShape(): array;
|
||||
}
|
||||
60
lib/public/TaskProcessing/ShapeDescriptor.php
Normal file
60
lib/public/TaskProcessing/ShapeDescriptor.php
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace OCP\TaskProcessing;
|
||||
|
||||
/**
|
||||
* Data object for input output shape entries
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class ShapeDescriptor implements \JsonSerializable {
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $description
|
||||
* @param EShapeType $shapeType
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $description,
|
||||
private EShapeType $shapeType,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getDescription(): string {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EShapeType
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getShapeType(): EShapeType {
|
||||
return $this->shapeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{name: string, description: string, type: "Number"|"Text"|"Audio"|"Image"|"Video"|"File"|"ListOfNumbers"|"ListOfTexts"|"ListOfImages"|"ListOfAudios"|"ListOfVideos"|"ListOfFiles"}
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function jsonSerialize(): array {
|
||||
/** @var "Number"|"Text"|"Audio"|"Image"|"Video"|"File"|"ListOfNumbers"|"ListOfTexts"|"ListOfImages"|"ListOfAudios"|"ListOfVideos"|"ListOfFiles" $type */
|
||||
$type = $this->getShapeType()->name;
|
||||
return [
|
||||
'name' => $this->getName(),
|
||||
'description' => $this->getDescription(),
|
||||
'type' => $type,
|
||||
];
|
||||
}
|
||||
}
|
||||
298
lib/public/TaskProcessing/Task.php
Normal file
298
lib/public/TaskProcessing/Task.php
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCP\TaskProcessing;
|
||||
|
||||
use DateTime;
|
||||
use OCP\TaskProcessing\Exception\ValidationException;
|
||||
|
||||
/**
|
||||
* This is a task processing task
|
||||
*
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final class Task implements \JsonSerializable {
|
||||
protected ?int $id = null;
|
||||
|
||||
protected ?DateTime $completionExpectedAt = null;
|
||||
|
||||
protected ?array $output = null;
|
||||
|
||||
protected ?string $errorMessage = null;
|
||||
|
||||
protected ?float $progress = null;
|
||||
|
||||
protected int $lastUpdated;
|
||||
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const STATUS_CANCELLED = 5;
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const STATUS_FAILED = 4;
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const STATUS_SUCCESSFUL = 3;
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const STATUS_RUNNING = 2;
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const STATUS_SCHEDULED = 1;
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const STATUS_UNKNOWN = 0;
|
||||
|
||||
/**
|
||||
* @psalm-var self::STATUS_*
|
||||
*/
|
||||
protected int $status = self::STATUS_UNKNOWN;
|
||||
|
||||
/**
|
||||
* @param string $taskTypeId
|
||||
* @param array<string,list<numeric|string>|numeric|string> $input
|
||||
* @param string $appId
|
||||
* @param string|null $userId
|
||||
* @param null|string $customId An arbitrary customId for this task. max length: 255 chars
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function __construct(
|
||||
protected readonly string $taskTypeId,
|
||||
protected array $input,
|
||||
protected readonly string $appId,
|
||||
protected readonly ?string $userId,
|
||||
protected readonly ?string $customId = '',
|
||||
) {
|
||||
$this->lastUpdated = time();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getTaskTypeId(): string {
|
||||
return $this->taskTypeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return self::STATUS_*
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getStatus(): int {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-param self::STATUS_* $status
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function setStatus(int $status): void {
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?DateTime $at
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function setCompletionExpectedAt(?DateTime $at): void {
|
||||
$this->completionExpectedAt = $at;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?DateTime
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getCompletionExpectedAt(): ?DateTime {
|
||||
return $this->completionExpectedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getId(): ?int {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $id
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function setId(?int $id): void {
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|array<array-key, list<numeric|string>|numeric|string> $output
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function setOutput(?array $output): void {
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array-key, list<numeric|string>|numeric|string>|null
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getOutput(): ?array {
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<array-key, list<numeric|string>|numeric|string>
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getInput(): array {
|
||||
return $this->input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getAppId(): string {
|
||||
return $this->appId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getCustomId(): ?string {
|
||||
return $this->customId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getUserId(): ?string {
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getLastUpdated(): int {
|
||||
return $this->lastUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $lastUpdated
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function setLastUpdated(int $lastUpdated): void {
|
||||
$this->lastUpdated = $lastUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-return array{id: ?int, lastUpdated: int, type: string, status: 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN', userId: ?string, appId: string, input: array<array-key, list<numeric|string>|numeric|string>, output: ?array<array-key, list<numeric|string>|numeric|string>, customId: ?string, completionExpectedAt: ?int, progress: ?float}
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function jsonSerialize(): array {
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
'type' => $this->getTaskTypeId(),
|
||||
'lastUpdated' => $this->getLastUpdated(),
|
||||
'status' => self::statusToString($this->getStatus()),
|
||||
'userId' => $this->getUserId(),
|
||||
'appId' => $this->getAppId(),
|
||||
'input' => $this->getInput(),
|
||||
'output' => $this->getOutput(),
|
||||
'customId' => $this->getCustomId(),
|
||||
'completionExpectedAt' => $this->getCompletionExpectedAt()?->getTimestamp(),
|
||||
'progress' => $this->getProgress(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $error
|
||||
* @return void
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function setErrorMessage(?string $error) {
|
||||
$this->errorMessage = $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getErrorMessage(): ?string {
|
||||
return $this->errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $input
|
||||
* @return void
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function setInput(array $input): void {
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float|null $progress
|
||||
* @return void
|
||||
* @throws ValidationException
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function setProgress(?float $progress): void {
|
||||
if ($progress < 0 || $progress > 1.0) {
|
||||
throw new ValidationException('Progress must be between 0.0 and 1.0 inclusively; ' . $progress . ' given');
|
||||
}
|
||||
$this->progress = $progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return float|null
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public function getProgress(): ?float {
|
||||
return $this->progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
* @return 'STATUS_CANCELLED'|'STATUS_FAILED'|'STATUS_SUCCESSFUL'|'STATUS_RUNNING'|'STATUS_SCHEDULED'|'STATUS_UNKNOWN'
|
||||
* @since 30.0.0
|
||||
*/
|
||||
final public static function statusToString(int $status): string {
|
||||
return match ($status) {
|
||||
self::STATUS_CANCELLED => 'STATUS_CANCELLED',
|
||||
self::STATUS_FAILED => 'STATUS_FAILED',
|
||||
self::STATUS_SUCCESSFUL => 'STATUS_SUCCESSFUL',
|
||||
self::STATUS_RUNNING => 'STATUS_RUNNING',
|
||||
self::STATUS_SCHEDULED => 'STATUS_SCHEDULED',
|
||||
default => 'STATUS_UNKNOWN',
|
||||
};
|
||||
}
|
||||
}
|
||||
108
lib/public/TaskProcessing/TaskTypes/AudioToText.php
Normal file
108
lib/public/TaskProcessing/TaskTypes/AudioToText.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCP\TaskProcessing\TaskTypes;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\TaskProcessing\EShapeType;
|
||||
use OCP\TaskProcessing\ITaskType;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
|
||||
/**
|
||||
* This is the task processing task type for generic transcription
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class AudioToText implements ITaskType {
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const ID = 'core:audio2text';
|
||||
|
||||
private IL10N $l;
|
||||
|
||||
/**
|
||||
* @param IFactory $l10nFactory
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
IFactory $l10nFactory,
|
||||
) {
|
||||
$this->l = $l10nFactory->get('core');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->l->t('Transcribe audio');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getDescription(): string {
|
||||
return $this->l->t('Transcribe the things said in an audio');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getId(): string {
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getInputShape(): array {
|
||||
return [
|
||||
'input' => new ShapeDescriptor(
|
||||
$this->l->t('Audio input'),
|
||||
$this->l->t('The audio to transcribe'),
|
||||
EShapeType::Audio
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getOutputShape(): array {
|
||||
return [
|
||||
'output' => new ShapeDescriptor(
|
||||
$this->l->t('Transcription'),
|
||||
$this->l->t('The transcribed text'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
113
lib/public/TaskProcessing/TaskTypes/ContextWrite.php
Normal file
113
lib/public/TaskProcessing/TaskTypes/ContextWrite.php
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCP\TaskProcessing\TaskTypes;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\TaskProcessing\EShapeType;
|
||||
use OCP\TaskProcessing\ITaskType;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
|
||||
/**
|
||||
* This is the task processing task type for generic text processing
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class ContextWrite implements ITaskType {
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const ID = 'core:contextwrite';
|
||||
|
||||
private IL10N $l;
|
||||
|
||||
/**
|
||||
* @param IFactory $l10nFactory
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
IFactory $l10nFactory,
|
||||
) {
|
||||
$this->l = $l10nFactory->get('core');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->l->t('ContextWrite');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getDescription(): string {
|
||||
return $this->l->t('Writes text in a given style based on the provided source material.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getId(): string {
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getInputShape(): array {
|
||||
return [
|
||||
'style_input' => new ShapeDescriptor(
|
||||
$this->l->t('Writing style'),
|
||||
$this->l->t('Demonstrate a writing style that you would like to immitate'),
|
||||
EShapeType::Text
|
||||
),
|
||||
'source_input' => new ShapeDescriptor(
|
||||
$this->l->t('Source material'),
|
||||
$this->l->t('The content that would like to be rewritten in the new writing style'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getOutputShape(): array {
|
||||
return [
|
||||
'output' => new ShapeDescriptor(
|
||||
$this->l->t('Generated text'),
|
||||
$this->l->t('The generated text with content from the source material in the given style'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
108
lib/public/TaskProcessing/TaskTypes/GenerateEmoji.php
Normal file
108
lib/public/TaskProcessing/TaskTypes/GenerateEmoji.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCP\TaskProcessing\TaskTypes;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\TaskProcessing\EShapeType;
|
||||
use OCP\TaskProcessing\ITaskType;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
|
||||
/**
|
||||
* This is the task processing task type for generic text processing
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class GenerateEmoji implements ITaskType {
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const ID = 'core:generateemoji';
|
||||
|
||||
private IL10N $l;
|
||||
|
||||
/**
|
||||
* @param IFactory $l10nFactory
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
IFactory $l10nFactory,
|
||||
) {
|
||||
$this->l = $l10nFactory->get('core');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->l->t('Emoji generator');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getDescription(): string {
|
||||
return $this->l->t('Takes text and generates a representative emoji for it.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getId(): string {
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getInputShape(): array {
|
||||
return [
|
||||
'input' => new ShapeDescriptor(
|
||||
$this->l->t('Input text'),
|
||||
$this->l->t('The text to generate an emoji for'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getOutputShape(): array {
|
||||
return [
|
||||
'output' => new ShapeDescriptor(
|
||||
$this->l->t('Generated emoji'),
|
||||
$this->l->t('The generated emoji based on the input text'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
113
lib/public/TaskProcessing/TaskTypes/TextToImage.php
Normal file
113
lib/public/TaskProcessing/TaskTypes/TextToImage.php
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCP\TaskProcessing\TaskTypes;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\TaskProcessing\EShapeType;
|
||||
use OCP\TaskProcessing\ITaskType;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
|
||||
/**
|
||||
* This is the task processing task type for image generation
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class TextToImage implements ITaskType {
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const ID = 'core:text2image';
|
||||
|
||||
private IL10N $l;
|
||||
|
||||
/**
|
||||
* @param IFactory $l10nFactory
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
IFactory $l10nFactory,
|
||||
) {
|
||||
$this->l = $l10nFactory->get('core');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->l->t('Generate image');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getDescription(): string {
|
||||
return $this->l->t('Generate an image from a text prompt');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getId(): string {
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getInputShape(): array {
|
||||
return [
|
||||
'input' => new ShapeDescriptor(
|
||||
$this->l->t('Prompt'),
|
||||
$this->l->t('Describe the image you want to generate'),
|
||||
EShapeType::Text
|
||||
),
|
||||
'numberOfImages' => new ShapeDescriptor(
|
||||
$this->l->t('Number of images'),
|
||||
$this->l->t('How many images to generate'),
|
||||
EShapeType::Number
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getOutputShape(): array {
|
||||
return [
|
||||
'images' => new ShapeDescriptor(
|
||||
$this->l->t('Output images'),
|
||||
$this->l->t('The generated images'),
|
||||
EShapeType::ListOfImages
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
108
lib/public/TaskProcessing/TaskTypes/TextToText.php
Normal file
108
lib/public/TaskProcessing/TaskTypes/TextToText.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCP\TaskProcessing\TaskTypes;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\TaskProcessing\EShapeType;
|
||||
use OCP\TaskProcessing\ITaskType;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
|
||||
/**
|
||||
* This is the task processing task type for generic text processing
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class TextToText implements ITaskType {
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const ID = 'core:text2text';
|
||||
|
||||
private IL10N $l;
|
||||
|
||||
/**
|
||||
* @param IFactory $l10nFactory
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
IFactory $l10nFactory,
|
||||
) {
|
||||
$this->l = $l10nFactory->get('core');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->l->t('Free text to text prompt');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getDescription(): string {
|
||||
return $this->l->t('Runs an arbitrary prompt through a language model that retuns a reply');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getId(): string {
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getInputShape(): array {
|
||||
return [
|
||||
'input' => new ShapeDescriptor(
|
||||
$this->l->t('Prompt'),
|
||||
$this->l->t('Describe a task that you want the assistant to do or ask a question'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getOutputShape(): array {
|
||||
return [
|
||||
'output' => new ShapeDescriptor(
|
||||
$this->l->t('Generated reply'),
|
||||
$this->l->t('The generated text from the assistant'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
108
lib/public/TaskProcessing/TaskTypes/TextToTextHeadline.php
Normal file
108
lib/public/TaskProcessing/TaskTypes/TextToTextHeadline.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCP\TaskProcessing\TaskTypes;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\TaskProcessing\EShapeType;
|
||||
use OCP\TaskProcessing\ITaskType;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
|
||||
/**
|
||||
* This is the task processing task type for creating headline
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class TextToTextHeadline implements ITaskType {
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const ID = 'core:text2text:headline';
|
||||
|
||||
private IL10N $l;
|
||||
|
||||
/**
|
||||
* @param IFactory $l10nFactory
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
IFactory $l10nFactory,
|
||||
) {
|
||||
$this->l = $l10nFactory->get('core');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->l->t('Generate a headline');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getDescription(): string {
|
||||
return $this->l->t('Generates a possible headline for a text.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getId(): string {
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getInputShape(): array {
|
||||
return [
|
||||
'input' => new ShapeDescriptor(
|
||||
$this->l->t('Original text'),
|
||||
$this->l->t('The original text to generate a headline for'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getOutputShape(): array {
|
||||
return [
|
||||
'output' => new ShapeDescriptor(
|
||||
$this->l->t('Headline'),
|
||||
$this->l->t('The generated headline'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
107
lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php
Normal file
107
lib/public/TaskProcessing/TaskTypes/TextToTextSummary.php
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCP\TaskProcessing\TaskTypes;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\TaskProcessing\EShapeType;
|
||||
use OCP\TaskProcessing\ITaskType;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
|
||||
/**
|
||||
* This is the task processing task type for summaries
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class TextToTextSummary implements ITaskType {
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const ID = 'core:text2text:summary';
|
||||
private IL10N $l;
|
||||
|
||||
/**
|
||||
* @param IFactory $l10nFactory
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
IFactory $l10nFactory,
|
||||
) {
|
||||
$this->l = $l10nFactory->get('core');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->l->t('Summarize');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getDescription(): string {
|
||||
return $this->l->t('Summarizes a text');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getId(): string {
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getInputShape(): array {
|
||||
return [
|
||||
'input' => new ShapeDescriptor(
|
||||
$this->l->t('Original text'),
|
||||
$this->l->t('The original text to summarize'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getOutputShape(): array {
|
||||
return [
|
||||
'output' => new ShapeDescriptor(
|
||||
$this->l->t('Summary'),
|
||||
$this->l->t('The generated summary'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
108
lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php
Normal file
108
lib/public/TaskProcessing/TaskTypes/TextToTextTopics.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @author Marcel Klehr <mklehr@gmx.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace OCP\TaskProcessing\TaskTypes;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\TaskProcessing\EShapeType;
|
||||
use OCP\TaskProcessing\ITaskType;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
|
||||
/**
|
||||
* This is the task processing task type for topics extraction
|
||||
* @since 30.0.0
|
||||
*/
|
||||
class TextToTextTopics implements ITaskType {
|
||||
/**
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public const ID = 'core:text2text:topics';
|
||||
|
||||
private IL10N $l;
|
||||
|
||||
/**
|
||||
* @param IFactory $l10nFactory
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
IFactory $l10nFactory,
|
||||
) {
|
||||
$this->l = $l10nFactory->get('core');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
return $this->l->t('Extract topics');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getDescription(): string {
|
||||
return $this->l->t('Extracts topics from a text and outputs them separated by commas');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getId(): string {
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getInputShape(): array {
|
||||
return [
|
||||
'input' => new ShapeDescriptor(
|
||||
$this->l->t('Original text'),
|
||||
$this->l->t('The original text to extract topics from'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ShapeDescriptor[]
|
||||
* @since 30.0.0
|
||||
*/
|
||||
public function getOutputShape(): array {
|
||||
return [
|
||||
'output' => new ShapeDescriptor(
|
||||
$this->l->t('Topics'),
|
||||
$this->l->t('The list of extracted topics'),
|
||||
EShapeType::Text
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
765
tests/lib/TaskProcessing/TaskProcessingTest.php
Normal file
765
tests/lib/TaskProcessing/TaskProcessingTest.php
Normal file
|
|
@ -0,0 +1,765 @@
|
|||
<?php
|
||||
/**
|
||||
* Copyright (c) 2024 Marcel Klehr <mklehr@gmx.net>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
namespace Test\TextProcessing;
|
||||
|
||||
use OC\AppFramework\Bootstrap\Coordinator;
|
||||
use OC\AppFramework\Bootstrap\RegistrationContext;
|
||||
use OC\AppFramework\Bootstrap\ServiceRegistration;
|
||||
use OC\EventDispatcher\EventDispatcher;
|
||||
use OC\TaskProcessing\Db\TaskMapper;
|
||||
use OC\TaskProcessing\Manager;
|
||||
use OC\TaskProcessing\RemoveOldTasksBackgroundJob;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\IJobList;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\AppData\IAppDataFactory;
|
||||
use OCP\Files\IAppData;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IServerContainer;
|
||||
use OCP\IUserManager;
|
||||
use OCP\SpeechToText\ISpeechToTextManager;
|
||||
use OCP\TaskProcessing\EShapeType;
|
||||
use OCP\TaskProcessing\Events\TaskFailedEvent;
|
||||
use OCP\TaskProcessing\Events\TaskSuccessfulEvent;
|
||||
use OCP\TaskProcessing\Exception\NotFoundException;
|
||||
use OCP\TaskProcessing\Exception\ProcessingException;
|
||||
use OCP\TaskProcessing\Exception\UnauthorizedException;
|
||||
use OCP\TaskProcessing\Exception\ValidationException;
|
||||
use OCP\TaskProcessing\IManager;
|
||||
use OCP\TaskProcessing\IProvider;
|
||||
use OCP\TaskProcessing\ISynchronousProvider;
|
||||
use OCP\TaskProcessing\ITaskType;
|
||||
use OCP\TaskProcessing\ShapeDescriptor;
|
||||
use OCP\TaskProcessing\Task;
|
||||
use OCP\TaskProcessing\TaskTypes\TextToImage;
|
||||
use OCP\TaskProcessing\TaskTypes\TextToText;
|
||||
use OCP\TaskProcessing\TaskTypes\TextToTextSummary;
|
||||
use OCP\TextProcessing\SummaryTaskType;
|
||||
use PHPUnit\Framework\Constraint\IsInstanceOf;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Test\BackgroundJob\DummyJobList;
|
||||
|
||||
class AudioToImage implements ITaskType {
|
||||
public const ID = 'test:audiotoimage';
|
||||
|
||||
public function getId(): string {
|
||||
return self::ID;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return self::class;
|
||||
}
|
||||
|
||||
public function getDescription(): string {
|
||||
return self::class;
|
||||
}
|
||||
|
||||
public function getInputShape(): array {
|
||||
return [
|
||||
'audio' => new ShapeDescriptor('Audio', 'The audio', EShapeType::Audio),
|
||||
];
|
||||
}
|
||||
|
||||
public function getOutputShape(): array {
|
||||
return [
|
||||
'spectrogram' => new ShapeDescriptor('Spectrogram', 'The audio spectrogram', EShapeType::Image),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class AsyncProvider implements IProvider {
|
||||
public function getId(): string {
|
||||
return 'test:sync:success';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return self::class;
|
||||
}
|
||||
|
||||
public function getTaskTypeId(): string {
|
||||
return AudioToImage::ID;
|
||||
}
|
||||
|
||||
public function getExpectedRuntime(): int {
|
||||
return 10;
|
||||
}
|
||||
|
||||
public function getOptionalInputShape(): array {
|
||||
return [
|
||||
'optionalKey' => new ShapeDescriptor('optional Key', 'AN optional key', EShapeType::Text),
|
||||
];
|
||||
}
|
||||
|
||||
public function getOptionalOutputShape(): array {
|
||||
return [
|
||||
'optionalKey' => new ShapeDescriptor('optional Key', 'AN optional key', EShapeType::Text),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class SuccessfulSyncProvider implements IProvider, ISynchronousProvider {
|
||||
public function getId(): string {
|
||||
return 'test:sync:success';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return self::class;
|
||||
}
|
||||
|
||||
public function getTaskTypeId(): string {
|
||||
return TextToText::ID;
|
||||
}
|
||||
|
||||
public function getExpectedRuntime(): int {
|
||||
return 10;
|
||||
}
|
||||
|
||||
public function getOptionalInputShape(): array {
|
||||
return [
|
||||
'optionalKey' => new ShapeDescriptor('optional Key', 'AN optional key', EShapeType::Text),
|
||||
];
|
||||
}
|
||||
|
||||
public function getOptionalOutputShape(): array {
|
||||
return [
|
||||
'optionalKey' => new ShapeDescriptor('optional Key', 'AN optional key', EShapeType::Text),
|
||||
];
|
||||
}
|
||||
|
||||
public function process(?string $userId, array $input, callable $reportProgress): array {
|
||||
return ['output' => $input['input']];
|
||||
}
|
||||
}
|
||||
|
||||
class FailingSyncProvider implements IProvider, ISynchronousProvider {
|
||||
public const ERROR_MESSAGE = 'Failure';
|
||||
public function getId(): string {
|
||||
return 'test:sync:fail';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return self::class;
|
||||
}
|
||||
|
||||
public function getTaskTypeId(): string {
|
||||
return TextToText::ID;
|
||||
}
|
||||
|
||||
public function getExpectedRuntime(): int {
|
||||
return 10;
|
||||
}
|
||||
|
||||
public function getOptionalInputShape(): array {
|
||||
return [
|
||||
'optionalKey' => new ShapeDescriptor('optional Key', 'AN optional key', EShapeType::Text),
|
||||
];
|
||||
}
|
||||
|
||||
public function getOptionalOutputShape(): array {
|
||||
return [
|
||||
'optionalKey' => new ShapeDescriptor('optional Key', 'AN optional key', EShapeType::Text),
|
||||
];
|
||||
}
|
||||
|
||||
public function process(?string $userId, array $input, callable $reportProgress): array {
|
||||
throw new ProcessingException(self::ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
class BrokenSyncProvider implements IProvider, ISynchronousProvider {
|
||||
public function getId(): string {
|
||||
return 'test:sync:broken-output';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return self::class;
|
||||
}
|
||||
|
||||
public function getTaskTypeId(): string {
|
||||
return TextToText::ID;
|
||||
}
|
||||
|
||||
public function getExpectedRuntime(): int {
|
||||
return 10;
|
||||
}
|
||||
|
||||
public function getOptionalInputShape(): array {
|
||||
return [
|
||||
'optionalKey' => new ShapeDescriptor('optional Key', 'AN optional key', EShapeType::Text),
|
||||
];
|
||||
}
|
||||
|
||||
public function getOptionalOutputShape(): array {
|
||||
return [
|
||||
'optionalKey' => new ShapeDescriptor('optional Key', 'AN optional key', EShapeType::Text),
|
||||
];
|
||||
}
|
||||
|
||||
public function process(?string $userId, array $input, callable $reportProgress): array {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class SuccessfulTextProcessingSummaryProvider implements \OCP\TextProcessing\IProvider {
|
||||
public bool $ran = false;
|
||||
|
||||
public function getName(): string {
|
||||
return 'TEST Vanilla LLM Provider';
|
||||
}
|
||||
|
||||
public function process(string $prompt): string {
|
||||
$this->ran = true;
|
||||
return $prompt . ' Summarize';
|
||||
}
|
||||
|
||||
public function getTaskType(): string {
|
||||
return SummaryTaskType::class;
|
||||
}
|
||||
}
|
||||
|
||||
class FailingTextProcessingSummaryProvider implements \OCP\TextProcessing\IProvider {
|
||||
public bool $ran = false;
|
||||
|
||||
public function getName(): string {
|
||||
return 'TEST Vanilla LLM Provider';
|
||||
}
|
||||
|
||||
public function process(string $prompt): string {
|
||||
$this->ran = true;
|
||||
throw new \Exception('ERROR');
|
||||
}
|
||||
|
||||
public function getTaskType(): string {
|
||||
return SummaryTaskType::class;
|
||||
}
|
||||
}
|
||||
|
||||
class SuccessfulTextToImageProvider implements \OCP\TextToImage\IProvider {
|
||||
public bool $ran = false;
|
||||
|
||||
public function getId(): string {
|
||||
return 'test:successful';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return 'TEST Provider';
|
||||
}
|
||||
|
||||
public function generate(string $prompt, array $resources): void {
|
||||
$this->ran = true;
|
||||
foreach($resources as $resource) {
|
||||
fwrite($resource, 'test');
|
||||
}
|
||||
}
|
||||
|
||||
public function getExpectedRuntime(): int {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
class FailingTextToImageProvider implements \OCP\TextToImage\IProvider {
|
||||
public bool $ran = false;
|
||||
|
||||
public function getId(): string {
|
||||
return 'test:failing';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return 'TEST Provider';
|
||||
}
|
||||
|
||||
public function generate(string $prompt, array $resources): void {
|
||||
$this->ran = true;
|
||||
throw new \RuntimeException('ERROR');
|
||||
}
|
||||
|
||||
public function getExpectedRuntime(): int {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group DB
|
||||
*/
|
||||
class TaskProcessingTest extends \Test\TestCase {
|
||||
private IManager $manager;
|
||||
private Coordinator $coordinator;
|
||||
private array $providers;
|
||||
private IServerContainer $serverContainer;
|
||||
private IEventDispatcher $eventDispatcher;
|
||||
private RegistrationContext $registrationContext;
|
||||
private TaskMapper $taskMapper;
|
||||
private IJobList $jobList;
|
||||
private IAppData $appData;
|
||||
private \OCP\Share\IManager $shareManager;
|
||||
private IRootFolder $rootFolder;
|
||||
|
||||
public const TEST_USER = 'testuser';
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->providers = [
|
||||
SuccessfulSyncProvider::class => new SuccessfulSyncProvider(),
|
||||
FailingSyncProvider::class => new FailingSyncProvider(),
|
||||
BrokenSyncProvider::class => new BrokenSyncProvider(),
|
||||
AsyncProvider::class => new AsyncProvider(),
|
||||
AudioToImage::class => new AudioToImage(),
|
||||
SuccessfulTextProcessingSummaryProvider::class => new SuccessfulTextProcessingSummaryProvider(),
|
||||
FailingTextProcessingSummaryProvider::class => new FailingTextProcessingSummaryProvider(),
|
||||
SuccessfulTextToImageProvider::class => new SuccessfulTextToImageProvider(),
|
||||
FailingTextToImageProvider::class => new FailingTextToImageProvider(),
|
||||
];
|
||||
|
||||
$userManager = \OCP\Server::get(IUserManager::class);
|
||||
if (!$userManager->userExists(self::TEST_USER)) {
|
||||
$userManager->createUser(self::TEST_USER, 'test');
|
||||
}
|
||||
|
||||
$this->serverContainer = $this->createMock(IServerContainer::class);
|
||||
$this->serverContainer->expects($this->any())->method('get')->willReturnCallback(function ($class) {
|
||||
return $this->providers[$class];
|
||||
});
|
||||
|
||||
$this->eventDispatcher = new EventDispatcher(
|
||||
new \Symfony\Component\EventDispatcher\EventDispatcher(),
|
||||
$this->serverContainer,
|
||||
\OC::$server->get(LoggerInterface::class),
|
||||
);
|
||||
|
||||
$this->registrationContext = $this->createMock(RegistrationContext::class);
|
||||
$this->coordinator = $this->createMock(Coordinator::class);
|
||||
$this->coordinator->expects($this->any())->method('getRegistrationContext')->willReturn($this->registrationContext);
|
||||
|
||||
$this->rootFolder = \OCP\Server::get(IRootFolder::class);
|
||||
|
||||
$this->taskMapper = \OCP\Server::get(TaskMapper::class);
|
||||
|
||||
$this->jobList = $this->createPartialMock(DummyJobList::class, ['add']);
|
||||
$this->jobList->expects($this->any())->method('add')->willReturnCallback(function () {
|
||||
});
|
||||
|
||||
$config = $this->createMock(IConfig::class);
|
||||
$config->method('getAppValue')
|
||||
->with('core', 'ai.textprocessing_provider_preferences', '')
|
||||
->willReturn('');
|
||||
|
||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
|
||||
$textProcessingManager = new \OC\TextProcessing\Manager(
|
||||
$this->serverContainer,
|
||||
$this->coordinator,
|
||||
\OC::$server->get(LoggerInterface::class),
|
||||
$this->jobList,
|
||||
\OC::$server->get(\OC\TextProcessing\Db\TaskMapper::class),
|
||||
\OC::$server->get(IConfig::class),
|
||||
);
|
||||
|
||||
$text2imageManager = new \OC\TextToImage\Manager(
|
||||
$this->serverContainer,
|
||||
$this->coordinator,
|
||||
\OC::$server->get(LoggerInterface::class),
|
||||
$this->jobList,
|
||||
\OC::$server->get(\OC\TextToImage\Db\TaskMapper::class),
|
||||
\OC::$server->get(IConfig::class),
|
||||
\OC::$server->get(IAppDataFactory::class),
|
||||
);
|
||||
|
||||
$this->shareManager = $this->createMock(\OCP\Share\IManager::class);
|
||||
|
||||
$this->manager = new Manager(
|
||||
$this->coordinator,
|
||||
$this->serverContainer,
|
||||
\OC::$server->get(LoggerInterface::class),
|
||||
$this->taskMapper,
|
||||
$this->jobList,
|
||||
$this->eventDispatcher,
|
||||
\OC::$server->get(IAppDataFactory::class),
|
||||
\OC::$server->get(IRootFolder::class),
|
||||
$textProcessingManager,
|
||||
$text2imageManager,
|
||||
\OC::$server->get(ISpeechToTextManager::class),
|
||||
$this->shareManager,
|
||||
);
|
||||
}
|
||||
|
||||
private function getFile(string $name, string $content): \OCP\Files\File {
|
||||
$folder = $this->rootFolder->getUserFolder(self::TEST_USER);
|
||||
$file = $folder->newFile($name, $content);
|
||||
return $file;
|
||||
}
|
||||
|
||||
public function testShouldNotHaveAnyProviders() {
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([]);
|
||||
self::assertCount(0, $this->manager->getAvailableTaskTypes());
|
||||
self::assertFalse($this->manager->hasProviders());
|
||||
self::expectException(\OCP\TaskProcessing\Exception\PreConditionNotMetException::class);
|
||||
$this->manager->scheduleTask(new Task(TextToText::ID, ['input' => 'Hello'], 'test', null));
|
||||
}
|
||||
|
||||
public function testProviderShouldBeRegisteredAndTaskFailValidation() {
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([
|
||||
new ServiceRegistration('test', BrokenSyncProvider::class)
|
||||
]);
|
||||
self::assertCount(1, $this->manager->getAvailableTaskTypes());
|
||||
self::assertTrue($this->manager->hasProviders());
|
||||
$task = new Task(TextToText::ID, ['wrongInputKey' => 'Hello'], 'test', null);
|
||||
self::assertNull($task->getId());
|
||||
self::expectException(ValidationException::class);
|
||||
$this->manager->scheduleTask($task);
|
||||
}
|
||||
|
||||
public function testProviderShouldBeRegisteredAndTaskWithFilesFailValidation() {
|
||||
$this->shareManager->expects($this->any())->method('getAccessList')->willReturn(['users' => []]);
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingTaskTypes')->willReturn([
|
||||
new ServiceRegistration('test', AudioToImage::class)
|
||||
]);
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([
|
||||
new ServiceRegistration('test', AsyncProvider::class)
|
||||
]);
|
||||
$this->shareManager->expects($this->any())->method('getAccessList')->willReturn(['users' => [null]]);
|
||||
self::assertCount(1, $this->manager->getAvailableTaskTypes());
|
||||
|
||||
self::assertTrue($this->manager->hasProviders());
|
||||
$audioId = $this->getFile('audioInput', 'Hello')->getId();
|
||||
$task = new Task(AudioToImage::ID, ['audio' => $audioId], 'test', null);
|
||||
self::assertNull($task->getId());
|
||||
self::assertEquals(Task::STATUS_UNKNOWN, $task->getStatus());
|
||||
self::expectException(UnauthorizedException::class);
|
||||
$this->manager->scheduleTask($task);
|
||||
}
|
||||
|
||||
public function testProviderShouldBeRegisteredAndFail() {
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([
|
||||
new ServiceRegistration('test', FailingSyncProvider::class)
|
||||
]);
|
||||
self::assertCount(1, $this->manager->getAvailableTaskTypes());
|
||||
self::assertTrue($this->manager->hasProviders());
|
||||
$task = new Task(TextToText::ID, ['input' => 'Hello'], 'test', null);
|
||||
self::assertNull($task->getId());
|
||||
self::assertEquals(Task::STATUS_UNKNOWN, $task->getStatus());
|
||||
$this->manager->scheduleTask($task);
|
||||
self::assertNotNull($task->getId());
|
||||
self::assertEquals(Task::STATUS_SCHEDULED, $task->getStatus());
|
||||
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new IsInstanceOf(TaskFailedEvent::class));
|
||||
|
||||
$backgroundJob = new \OC\TaskProcessing\SynchronousBackgroundJob(
|
||||
\OCP\Server::get(ITimeFactory::class),
|
||||
$this->manager,
|
||||
$this->jobList,
|
||||
\OCP\Server::get(LoggerInterface::class),
|
||||
);
|
||||
$backgroundJob->start($this->jobList);
|
||||
|
||||
$task = $this->manager->getTask($task->getId());
|
||||
self::assertEquals(Task::STATUS_FAILED, $task->getStatus());
|
||||
self::assertEquals(FailingSyncProvider::ERROR_MESSAGE, $task->getErrorMessage());
|
||||
}
|
||||
|
||||
public function testProviderShouldBeRegisteredAndFailOutputValidation() {
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([
|
||||
new ServiceRegistration('test', BrokenSyncProvider::class)
|
||||
]);
|
||||
self::assertCount(1, $this->manager->getAvailableTaskTypes());
|
||||
self::assertTrue($this->manager->hasProviders());
|
||||
$task = new Task(TextToText::ID, ['input' => 'Hello'], 'test', null);
|
||||
self::assertNull($task->getId());
|
||||
self::assertEquals(Task::STATUS_UNKNOWN, $task->getStatus());
|
||||
$this->manager->scheduleTask($task);
|
||||
self::assertNotNull($task->getId());
|
||||
self::assertEquals(Task::STATUS_SCHEDULED, $task->getStatus());
|
||||
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new IsInstanceOf(TaskFailedEvent::class));
|
||||
|
||||
$backgroundJob = new \OC\TaskProcessing\SynchronousBackgroundJob(
|
||||
\OCP\Server::get(ITimeFactory::class),
|
||||
$this->manager,
|
||||
$this->jobList,
|
||||
\OCP\Server::get(LoggerInterface::class),
|
||||
);
|
||||
$backgroundJob->start($this->jobList);
|
||||
|
||||
$task = $this->manager->getTask($task->getId());
|
||||
self::assertEquals(Task::STATUS_FAILED, $task->getStatus());
|
||||
self::assertEquals('The task was processed successfully but the provider\'s output doesn\'t pass validation against the task type\'s outputShape spec and/or the provider\'s own optionalOutputShape spec', $task->getErrorMessage());
|
||||
}
|
||||
|
||||
public function testProviderShouldBeRegisteredAndRun() {
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([
|
||||
new ServiceRegistration('test', SuccessfulSyncProvider::class)
|
||||
]);
|
||||
self::assertCount(1, $this->manager->getAvailableTaskTypes());
|
||||
$taskTypeStruct = $this->manager->getAvailableTaskTypes()[array_keys($this->manager->getAvailableTaskTypes())[0]];
|
||||
self::assertTrue(isset($taskTypeStruct['inputShape']['input']));
|
||||
self::assertEquals(EShapeType::Text, $taskTypeStruct['inputShape']['input']->getShapeType());
|
||||
self::assertTrue(isset($taskTypeStruct['optionalInputShape']['optionalKey']));
|
||||
self::assertEquals(EShapeType::Text, $taskTypeStruct['optionalInputShape']['optionalKey']->getShapeType());
|
||||
self::assertTrue(isset($taskTypeStruct['outputShape']['output']));
|
||||
self::assertEquals(EShapeType::Text, $taskTypeStruct['outputShape']['output']->getShapeType());
|
||||
self::assertTrue(isset($taskTypeStruct['optionalOutputShape']['optionalKey']));
|
||||
self::assertEquals(EShapeType::Text, $taskTypeStruct['optionalOutputShape']['optionalKey']->getShapeType());
|
||||
|
||||
self::assertTrue($this->manager->hasProviders());
|
||||
$task = new Task(TextToText::ID, ['input' => 'Hello'], 'test', null);
|
||||
self::assertNull($task->getId());
|
||||
self::assertEquals(Task::STATUS_UNKNOWN, $task->getStatus());
|
||||
$this->manager->scheduleTask($task);
|
||||
self::assertNotNull($task->getId());
|
||||
self::assertEquals(Task::STATUS_SCHEDULED, $task->getStatus());
|
||||
|
||||
// Task object retrieved from db is up-to-date
|
||||
$task2 = $this->manager->getTask($task->getId());
|
||||
self::assertEquals($task->getId(), $task2->getId());
|
||||
self::assertEquals(['input' => 'Hello'], $task2->getInput());
|
||||
self::assertNull($task2->getOutput());
|
||||
self::assertEquals(Task::STATUS_SCHEDULED, $task2->getStatus());
|
||||
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new IsInstanceOf(TaskSuccessfulEvent::class));
|
||||
|
||||
$backgroundJob = new \OC\TaskProcessing\SynchronousBackgroundJob(
|
||||
\OCP\Server::get(ITimeFactory::class),
|
||||
$this->manager,
|
||||
$this->jobList,
|
||||
\OCP\Server::get(LoggerInterface::class),
|
||||
);
|
||||
$backgroundJob->start($this->jobList);
|
||||
|
||||
$task = $this->manager->getTask($task->getId());
|
||||
self::assertEquals(Task::STATUS_SUCCESSFUL, $task->getStatus(), 'Status is '. $task->getStatus() . ' with error message: ' . $task->getErrorMessage());
|
||||
self::assertEquals(['output' => 'Hello'], $task->getOutput());
|
||||
self::assertEquals(1, $task->getProgress());
|
||||
}
|
||||
|
||||
public function testAsyncProviderWithFilesShouldBeRegisteredAndRun() {
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingTaskTypes')->willReturn([
|
||||
new ServiceRegistration('test', AudioToImage::class)
|
||||
]);
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([
|
||||
new ServiceRegistration('test', AsyncProvider::class)
|
||||
]);
|
||||
$this->shareManager->expects($this->any())->method('getAccessList')->willReturn(['users' => ['testuser' => 1]]);
|
||||
self::assertCount(1, $this->manager->getAvailableTaskTypes());
|
||||
|
||||
self::assertTrue($this->manager->hasProviders());
|
||||
$audioId = $this->getFile('audioInput', 'Hello')->getId();
|
||||
$task = new Task(AudioToImage::ID, ['audio' => $audioId], 'test', 'testuser');
|
||||
self::assertNull($task->getId());
|
||||
self::assertEquals(Task::STATUS_UNKNOWN, $task->getStatus());
|
||||
$this->manager->scheduleTask($task);
|
||||
self::assertNotNull($task->getId());
|
||||
self::assertEquals(Task::STATUS_SCHEDULED, $task->getStatus());
|
||||
|
||||
// Task object retrieved from db is up-to-date
|
||||
$task2 = $this->manager->getTask($task->getId());
|
||||
self::assertEquals($task->getId(), $task2->getId());
|
||||
self::assertEquals(['audio' => $audioId], $task2->getInput());
|
||||
self::assertNull($task2->getOutput());
|
||||
self::assertEquals(Task::STATUS_SCHEDULED, $task2->getStatus());
|
||||
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new IsInstanceOf(TaskSuccessfulEvent::class));
|
||||
|
||||
$this->manager->setTaskProgress($task2->getId(), 0.1);
|
||||
$input = $this->manager->prepareInputData($task2);
|
||||
self::assertTrue(isset($input['audio']));
|
||||
self::assertInstanceOf(\OCP\Files\File::class, $input['audio']);
|
||||
self::assertEquals($audioId, $input['audio']->getId());
|
||||
|
||||
$this->manager->setTaskResult($task2->getId(), null, ['spectrogram' => 'World']);
|
||||
|
||||
$task = $this->manager->getTask($task->getId());
|
||||
self::assertEquals(Task::STATUS_SUCCESSFUL, $task->getStatus());
|
||||
self::assertEquals(1, $task->getProgress());
|
||||
self::assertTrue(isset($task->getOutput()['spectrogram']));
|
||||
$node = $this->rootFolder->getFirstNodeByIdInPath($task->getOutput()['spectrogram'], '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
|
||||
self::assertNotNull($node);
|
||||
self::assertInstanceOf(\OCP\Files\File::class, $node);
|
||||
self::assertEquals('World', $node->getContent());
|
||||
}
|
||||
|
||||
public function testNonexistentTask() {
|
||||
$this->expectException(\OCP\TaskProcessing\Exception\NotFoundException::class);
|
||||
$this->manager->getTask(2147483646);
|
||||
}
|
||||
|
||||
public function testOldTasksShouldBeCleanedUp() {
|
||||
$currentTime = new \DateTime('now');
|
||||
$timeFactory = $this->createMock(ITimeFactory::class);
|
||||
$timeFactory->expects($this->any())->method('getDateTime')->willReturnCallback(fn () => $currentTime);
|
||||
$timeFactory->expects($this->any())->method('getTime')->willReturnCallback(fn () => $currentTime->getTimestamp());
|
||||
|
||||
$this->taskMapper = new TaskMapper(
|
||||
\OCP\Server::get(IDBConnection::class),
|
||||
$timeFactory,
|
||||
);
|
||||
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([
|
||||
new ServiceRegistration('test', SuccessfulSyncProvider::class)
|
||||
]);
|
||||
self::assertCount(1, $this->manager->getAvailableTaskTypes());
|
||||
self::assertTrue($this->manager->hasProviders());
|
||||
$task = new Task(TextToText::ID, ['input' => 'Hello'], 'test', null);
|
||||
$this->manager->scheduleTask($task);
|
||||
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new IsInstanceOf(TaskSuccessfulEvent::class));
|
||||
|
||||
$backgroundJob = new \OC\TaskProcessing\SynchronousBackgroundJob(
|
||||
\OCP\Server::get(ITimeFactory::class),
|
||||
$this->manager,
|
||||
$this->jobList,
|
||||
\OCP\Server::get(LoggerInterface::class),
|
||||
);
|
||||
$backgroundJob->start($this->jobList);
|
||||
|
||||
$task = $this->manager->getTask($task->getId());
|
||||
|
||||
$currentTime = $currentTime->add(new \DateInterval('P1Y'));
|
||||
// run background job
|
||||
$bgJob = new RemoveOldTasksBackgroundJob(
|
||||
$timeFactory,
|
||||
$this->taskMapper,
|
||||
\OC::$server->get(LoggerInterface::class),
|
||||
\OCP\Server::get(IAppDataFactory::class),
|
||||
);
|
||||
$bgJob->setArgument([]);
|
||||
$bgJob->start($this->jobList);
|
||||
|
||||
$this->expectException(NotFoundException::class);
|
||||
$this->manager->getTask($task->getId());
|
||||
}
|
||||
|
||||
public function testShouldTransparentlyHandleTextProcessingProviders() {
|
||||
$this->registrationContext->expects($this->any())->method('getTextProcessingProviders')->willReturn([
|
||||
new ServiceRegistration('test', SuccessfulTextProcessingSummaryProvider::class)
|
||||
]);
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([
|
||||
]);
|
||||
$taskTypes = $this->manager->getAvailableTaskTypes();
|
||||
self::assertCount(1, $taskTypes);
|
||||
self::assertTrue(isset($taskTypes[TextToTextSummary::ID]));
|
||||
self::assertTrue($this->manager->hasProviders());
|
||||
$task = new Task(TextToTextSummary::ID, ['input' => 'Hello'], 'test', null);
|
||||
$this->manager->scheduleTask($task);
|
||||
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new IsInstanceOf(TaskSuccessfulEvent::class));
|
||||
|
||||
$backgroundJob = new \OC\TaskProcessing\SynchronousBackgroundJob(
|
||||
\OCP\Server::get(ITimeFactory::class),
|
||||
$this->manager,
|
||||
$this->jobList,
|
||||
\OCP\Server::get(LoggerInterface::class),
|
||||
);
|
||||
$backgroundJob->start($this->jobList);
|
||||
|
||||
$task = $this->manager->getTask($task->getId());
|
||||
self::assertEquals(Task::STATUS_SUCCESSFUL, $task->getStatus());
|
||||
self::assertIsArray($task->getOutput());
|
||||
self::assertTrue(isset($task->getOutput()['output']));
|
||||
self::assertEquals('Hello Summarize', $task->getOutput()['output']);
|
||||
self::assertTrue($this->providers[SuccessfulTextProcessingSummaryProvider::class]->ran);
|
||||
}
|
||||
|
||||
public function testShouldTransparentlyHandleFailingTextProcessingProviders() {
|
||||
$this->registrationContext->expects($this->any())->method('getTextProcessingProviders')->willReturn([
|
||||
new ServiceRegistration('test', FailingTextProcessingSummaryProvider::class)
|
||||
]);
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([
|
||||
]);
|
||||
$taskTypes = $this->manager->getAvailableTaskTypes();
|
||||
self::assertCount(1, $taskTypes);
|
||||
self::assertTrue(isset($taskTypes[TextToTextSummary::ID]));
|
||||
self::assertTrue($this->manager->hasProviders());
|
||||
$task = new Task(TextToTextSummary::ID, ['input' => 'Hello'], 'test', null);
|
||||
$this->manager->scheduleTask($task);
|
||||
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new IsInstanceOf(TaskFailedEvent::class));
|
||||
|
||||
$backgroundJob = new \OC\TaskProcessing\SynchronousBackgroundJob(
|
||||
\OCP\Server::get(ITimeFactory::class),
|
||||
$this->manager,
|
||||
$this->jobList,
|
||||
\OCP\Server::get(LoggerInterface::class),
|
||||
);
|
||||
$backgroundJob->start($this->jobList);
|
||||
|
||||
$task = $this->manager->getTask($task->getId());
|
||||
self::assertEquals(Task::STATUS_FAILED, $task->getStatus());
|
||||
self::assertTrue($task->getOutput() === null);
|
||||
self::assertEquals('ERROR', $task->getErrorMessage());
|
||||
self::assertTrue($this->providers[FailingTextProcessingSummaryProvider::class]->ran);
|
||||
}
|
||||
|
||||
public function testShouldTransparentlyHandleText2ImageProviders() {
|
||||
$this->registrationContext->expects($this->any())->method('getTextToImageProviders')->willReturn([
|
||||
new ServiceRegistration('test', SuccessfulTextToImageProvider::class)
|
||||
]);
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([
|
||||
]);
|
||||
$taskTypes = $this->manager->getAvailableTaskTypes();
|
||||
self::assertCount(1, $taskTypes);
|
||||
self::assertTrue(isset($taskTypes[TextToImage::ID]));
|
||||
self::assertTrue($this->manager->hasProviders());
|
||||
$task = new Task(TextToImage::ID, ['input' => 'Hello', 'numberOfImages' => 3], 'test', null);
|
||||
$this->manager->scheduleTask($task);
|
||||
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new IsInstanceOf(TaskSuccessfulEvent::class));
|
||||
|
||||
$backgroundJob = new \OC\TaskProcessing\SynchronousBackgroundJob(
|
||||
\OCP\Server::get(ITimeFactory::class),
|
||||
$this->manager,
|
||||
$this->jobList,
|
||||
\OCP\Server::get(LoggerInterface::class),
|
||||
);
|
||||
$backgroundJob->start($this->jobList);
|
||||
|
||||
$task = $this->manager->getTask($task->getId());
|
||||
self::assertEquals(Task::STATUS_SUCCESSFUL, $task->getStatus());
|
||||
self::assertIsArray($task->getOutput());
|
||||
self::assertTrue(isset($task->getOutput()['images']));
|
||||
self::assertIsArray($task->getOutput()['images']);
|
||||
self::assertCount(3, $task->getOutput()['images']);
|
||||
self::assertTrue($this->providers[SuccessfulTextToImageProvider::class]->ran);
|
||||
$node = $this->rootFolder->getFirstNodeByIdInPath($task->getOutput()['images'][0], '/' . $this->rootFolder->getAppDataDirectoryName() . '/');
|
||||
self::assertNotNull($node);
|
||||
self::assertInstanceOf(\OCP\Files\File::class, $node);
|
||||
self::assertEquals('test', $node->getContent());
|
||||
}
|
||||
|
||||
public function testShouldTransparentlyHandleFailingText2ImageProviders() {
|
||||
$this->registrationContext->expects($this->any())->method('getTextToImageProviders')->willReturn([
|
||||
new ServiceRegistration('test', FailingTextToImageProvider::class)
|
||||
]);
|
||||
$this->registrationContext->expects($this->any())->method('getTaskProcessingProviders')->willReturn([
|
||||
]);
|
||||
$taskTypes = $this->manager->getAvailableTaskTypes();
|
||||
self::assertCount(1, $taskTypes);
|
||||
self::assertTrue(isset($taskTypes[TextToImage::ID]));
|
||||
self::assertTrue($this->manager->hasProviders());
|
||||
$task = new Task(TextToImage::ID, ['input' => 'Hello', 'numberOfImages' => 3], 'test', null);
|
||||
$this->manager->scheduleTask($task);
|
||||
|
||||
$this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new IsInstanceOf(TaskFailedEvent::class));
|
||||
|
||||
$backgroundJob = new \OC\TaskProcessing\SynchronousBackgroundJob(
|
||||
\OCP\Server::get(ITimeFactory::class),
|
||||
$this->manager,
|
||||
$this->jobList,
|
||||
\OCP\Server::get(LoggerInterface::class),
|
||||
);
|
||||
$backgroundJob->start($this->jobList);
|
||||
|
||||
$task = $this->manager->getTask($task->getId());
|
||||
self::assertEquals(Task::STATUS_FAILED, $task->getStatus());
|
||||
self::assertTrue($task->getOutput() === null);
|
||||
self::assertEquals('ERROR', $task->getErrorMessage());
|
||||
self::assertTrue($this->providers[FailingTextToImageProvider::class]->ran);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
// between betas, final and RCs. This is _not_ the public version number. Reset minor/patch level
|
||||
// when updating major/minor version number.
|
||||
|
||||
$OC_Version = [30, 0, 0, 0];
|
||||
$OC_Version = [30, 0, 0, 1];
|
||||
|
||||
// The human-readable string
|
||||
$OC_VersionString = '30.0.0 dev';
|
||||
|
|
|
|||
Loading…
Reference in a new issue