Browse Source
Merge pull request #40326 from nextcloud/enh/text-to-image-api
Merge pull request #40326 from nextcloud/enh/text-to-image-api
Implement TextToImage OCP APIpull/41135/head
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2746 additions and 8 deletions
-
2apps/settings/lib/Controller/AISettingsController.php
-
11apps/settings/lib/Settings/Admin/ArtificialIntelligence.php
-
28apps/settings/src/components/AdminAI.vue
-
1apps/testing/composer/composer/autoload_classmap.php
-
1apps/testing/composer/composer/autoload_static.php
-
BINapps/testing/img/logo.png
-
2apps/testing/lib/AppInfo/Application.php
-
48apps/testing/lib/Provider/FakeText2ImageProvider.php
-
246core/Controller/TextToImageApiController.php
-
99core/Migrations/Version28000Date20230906104802.php
-
11core/ResponseDefinitions.php
-
876core/openapi.json
-
7core/routes.php
-
16lib/composer/composer/autoload_classmap.php
-
16lib/composer/composer/autoload_static.php
-
24lib/private/AppFramework/Bootstrap/RegistrationContext.php
-
2lib/private/Files/Node/Folder.php
-
8lib/private/Repair/AddRemoveOldTasksBackgroundJob.php
-
2lib/private/Server.php
-
117lib/private/TextToImage/Db/Task.php
-
127lib/private/TextToImage/Db/TaskMapper.php
-
334lib/private/TextToImage/Manager.php
-
78lib/private/TextToImage/RemoveOldTasksBackgroundJob.php
-
63lib/private/TextToImage/TaskBackgroundJob.php
-
11lib/public/AppFramework/Bootstrap/IRegistrationContext.php
-
52lib/public/TextToImage/Events/AbstractTextToImageEvent.php
-
54lib/public/TextToImage/Events/TaskFailedEvent.php
-
33lib/public/TextToImage/Events/TaskSuccessfulEvent.php
-
31lib/public/TextToImage/Exception/TaskFailureException.php
-
31lib/public/TextToImage/Exception/TaskNotFoundException.php
-
31lib/public/TextToImage/Exception/TextToImageException.php
-
116lib/public/TextToImage/IManager.php
-
64lib/public/TextToImage/IProvider.php
-
212lib/public/TextToImage/Task.php
After Width: 252 | Height: 120 | Size: 3.5 KiB |
@ -0,0 +1,48 @@ |
|||||
|
<?php |
||||
|
/** |
||||
|
* @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 OCA\Testing\Provider; |
||||
|
|
||||
|
use OCP\TextToImage\IProvider; |
||||
|
|
||||
|
class FakeText2ImageProvider implements IProvider { |
||||
|
|
||||
|
public function getName(): string { |
||||
|
return 'Fake Text2Image provider'; |
||||
|
} |
||||
|
|
||||
|
public function generate(string $prompt, array $resources): void { |
||||
|
foreach ($resources as $resource) { |
||||
|
$read = fopen(__DIR__ . '/../../img/logo.png', 'r'); |
||||
|
stream_copy_to_stream($read, $resource); |
||||
|
fclose($read); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function getExpectedRuntime(): int { |
||||
|
return 1; |
||||
|
} |
||||
|
|
||||
|
public function getId(): string { |
||||
|
return 'testing-fake-text2image-provider'; |
||||
|
} |
||||
|
} |
@ -0,0 +1,246 @@ |
|||||
|
<?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\Core\Controller; |
||||
|
|
||||
|
use OC\Files\AppData\AppData; |
||||
|
use OCA\Core\ResponseDefinitions; |
||||
|
use OCP\AppFramework\Http; |
||||
|
use OCP\AppFramework\Http\Attribute\AnonRateLimit; |
||||
|
use OCP\AppFramework\Http\Attribute\BruteForceProtection; |
||||
|
use OCP\AppFramework\Http\Attribute\NoAdminRequired; |
||||
|
use OCP\AppFramework\Http\Attribute\PublicPage; |
||||
|
use OCP\AppFramework\Http\Attribute\UserRateLimit; |
||||
|
use OCP\AppFramework\Http\DataResponse; |
||||
|
use OCP\AppFramework\Http\FileDisplayResponse; |
||||
|
use OCP\DB\Exception; |
||||
|
use OCP\Files\NotFoundException; |
||||
|
use OCP\IL10N; |
||||
|
use OCP\IRequest; |
||||
|
use OCP\TextToImage\Exception\TaskFailureException; |
||||
|
use OCP\TextToImage\Exception\TaskNotFoundException; |
||||
|
use OCP\TextToImage\Task; |
||||
|
use OCP\TextToImage\IManager; |
||||
|
use OCP\PreConditionNotMetException; |
||||
|
|
||||
|
/** |
||||
|
* @psalm-import-type CoreTextToImageTask from ResponseDefinitions |
||||
|
*/ |
||||
|
class TextToImageApiController extends \OCP\AppFramework\OCSController { |
||||
|
public function __construct( |
||||
|
string $appName, |
||||
|
IRequest $request, |
||||
|
private IManager $textToImageManager, |
||||
|
private IL10N $l, |
||||
|
private ?string $userId, |
||||
|
private AppData $appData, |
||||
|
) { |
||||
|
parent::__construct($appName, $request); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Check whether this feature is available |
||||
|
* |
||||
|
* @return DataResponse<Http::STATUS_OK, array{isAvailable: bool}, array{}> |
||||
|
* |
||||
|
* 200: Returns availability status |
||||
|
*/ |
||||
|
#[PublicPage]
|
||||
|
public function isAvailable(): DataResponse { |
||||
|
return new DataResponse([ |
||||
|
'isAvailable' => $this->textToImageManager->hasProviders(), |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* This endpoint allows scheduling a text to image task |
||||
|
* |
||||
|
* @param string $input Input text |
||||
|
* @param string $appId ID of the app that will execute the task |
||||
|
* @param string $identifier An arbitrary identifier for the task |
||||
|
* @param int $numberOfImages The number of images to generate |
||||
|
* |
||||
|
* @return DataResponse<Http::STATUS_OK, array{task: CoreTextToImageTask}, array{}>|DataResponse<Http::STATUS_PRECONDITION_FAILED|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}> |
||||
|
* |
||||
|
* 200: Task scheduled successfully |
||||
|
* 412: Scheduling task is not possible |
||||
|
*/ |
||||
|
#[PublicPage]
|
||||
|
#[UserRateLimit(limit: 20, period: 120)]
|
||||
|
#[AnonRateLimit(limit: 5, period: 120)]
|
||||
|
public function schedule(string $input, string $appId, string $identifier = '', int $numberOfImages = 8): DataResponse { |
||||
|
$task = new Task($input, $appId, $numberOfImages, $this->userId, $identifier); |
||||
|
try { |
||||
|
try { |
||||
|
$this->textToImageManager->runOrScheduleTask($task); |
||||
|
} catch (TaskFailureException) { |
||||
|
// Task status was already updated by the manager, nothing to do here
|
||||
|
} |
||||
|
|
||||
|
$json = $task->jsonSerialize(); |
||||
|
|
||||
|
return new DataResponse([ |
||||
|
'task' => $json, |
||||
|
]); |
||||
|
} catch (PreConditionNotMetException) { |
||||
|
return new DataResponse(['message' => $this->l->t('No text to image provider is available')], Http::STATUS_PRECONDITION_FAILED); |
||||
|
} catch (Exception) { |
||||
|
return new DataResponse(['message' => $this->l->t('Internal 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: CoreTextToImageTask}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}> |
||||
|
* |
||||
|
* 200: Task returned |
||||
|
* 404: Task not found |
||||
|
*/ |
||||
|
#[PublicPage]
|
||||
|
#[BruteForceProtection(action: 'text2image')]
|
||||
|
public function getTask(int $id): DataResponse { |
||||
|
try { |
||||
|
$task = $this->textToImageManager->getUserTask($id, $this->userId); |
||||
|
|
||||
|
$json = $task->jsonSerialize(); |
||||
|
|
||||
|
return new DataResponse([ |
||||
|
'task' => $json, |
||||
|
]); |
||||
|
} catch (TaskNotFoundException) { |
||||
|
$res = new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND); |
||||
|
$res->throttle(['action' => 'text2image']); |
||||
|
return $res; |
||||
|
} catch (\RuntimeException) { |
||||
|
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* This endpoint allows downloading the resulting image of a task |
||||
|
* |
||||
|
* @param int $id The id of the task |
||||
|
* @param int $index The index of the image to retrieve |
||||
|
* |
||||
|
* @return FileDisplayResponse<Http::STATUS_OK, array{'Content-Type': string}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}> |
||||
|
* |
||||
|
* 200: Image returned |
||||
|
* 404: Task or image not found |
||||
|
*/ |
||||
|
#[PublicPage]
|
||||
|
#[BruteForceProtection(action: 'text2image')]
|
||||
|
public function getImage(int $id, int $index): DataResponse|FileDisplayResponse { |
||||
|
try { |
||||
|
$task = $this->textToImageManager->getUserTask($id, $this->userId); |
||||
|
try { |
||||
|
$folder = $this->appData->getFolder('text2image'); |
||||
|
} catch(NotFoundException) { |
||||
|
$res = new DataResponse(['message' => $this->l->t('Image not found')], Http::STATUS_NOT_FOUND); |
||||
|
$res->throttle(['action' => 'text2image']); |
||||
|
return $res; |
||||
|
} |
||||
|
$file = $folder->getFolder((string) $task->getId())->getFile((string) $index); |
||||
|
$info = getimagesizefromstring($file->getContent()); |
||||
|
|
||||
|
return new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => image_type_to_mime_type($info[2])]); |
||||
|
} catch (TaskNotFoundException) { |
||||
|
$res = new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND); |
||||
|
$res->throttle(['action' => 'text2image']); |
||||
|
return $res; |
||||
|
} catch (\RuntimeException) { |
||||
|
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); |
||||
|
} catch (NotFoundException) { |
||||
|
$res = new DataResponse(['message' => $this->l->t('Image not found')], Http::STATUS_NOT_FOUND); |
||||
|
$res->throttle(['action' => 'text2image']); |
||||
|
return $res; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* This endpoint allows to delete a scheduled task for a user |
||||
|
* |
||||
|
* @param int $id The id of the task |
||||
|
* |
||||
|
* @return DataResponse<Http::STATUS_OK, array{task: CoreTextToImageTask}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}> |
||||
|
* |
||||
|
* 200: Task returned |
||||
|
* 404: Task not found |
||||
|
*/ |
||||
|
#[NoAdminRequired]
|
||||
|
#[BruteForceProtection(action: 'text2image')]
|
||||
|
public function deleteTask(int $id): DataResponse { |
||||
|
try { |
||||
|
$task = $this->textToImageManager->getUserTask($id, $this->userId); |
||||
|
|
||||
|
$this->textToImageManager->deleteTask($task); |
||||
|
|
||||
|
$json = $task->jsonSerialize(); |
||||
|
|
||||
|
return new DataResponse([ |
||||
|
'task' => $json, |
||||
|
]); |
||||
|
} catch (TaskNotFoundException) { |
||||
|
$res = new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND); |
||||
|
$res->throttle(['action' => 'text2image']); |
||||
|
return $res; |
||||
|
} catch (\RuntimeException) { |
||||
|
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 $identifier An arbitrary identifier for the task |
||||
|
* @return DataResponse<Http::STATUS_OK, array{tasks: CoreTextToImageTask[]}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}> |
||||
|
* |
||||
|
* 200: Task list returned |
||||
|
*/ |
||||
|
#[NoAdminRequired]
|
||||
|
#[AnonRateLimit(limit: 5, period: 120)]
|
||||
|
public function listTasksByApp(string $appId, ?string $identifier = null): DataResponse { |
||||
|
try { |
||||
|
$tasks = $this->textToImageManager->getUserTasksByApp($this->userId, $appId, $identifier); |
||||
|
/** @var CoreTextToImageTask[] $json */ |
||||
|
$json = array_map(static function (Task $task) { |
||||
|
return $task->jsonSerialize(); |
||||
|
}, $tasks); |
||||
|
|
||||
|
return new DataResponse([ |
||||
|
'tasks' => $json, |
||||
|
]); |
||||
|
} catch (\RuntimeException) { |
||||
|
return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,99 @@ |
|||||
|
<?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\Core\Migrations; |
||||
|
|
||||
|
use Closure; |
||||
|
use OCP\DB\ISchemaWrapper; |
||||
|
use OCP\DB\Types; |
||||
|
use OCP\Migration\IOutput; |
||||
|
use OCP\Migration\SimpleMigrationStep; |
||||
|
|
||||
|
/** |
||||
|
* Introduce text2image_tasks table |
||||
|
*/ |
||||
|
class Version28000Date20230906104802 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('text2image_tasks')) { |
||||
|
$table = $schema->createTable('text2image_tasks'); |
||||
|
|
||||
|
$table->addColumn('id', Types::BIGINT, [ |
||||
|
'notnull' => true, |
||||
|
'length' => 64, |
||||
|
'autoincrement' => true, |
||||
|
]); |
||||
|
$table->addColumn('input', Types::TEXT, [ |
||||
|
'notnull' => true, |
||||
|
]); |
||||
|
$table->addColumn('status', Types::INTEGER, [ |
||||
|
'notnull' => false, |
||||
|
'length' => 6, |
||||
|
'default' => 0, |
||||
|
]); |
||||
|
$table->addColumn('number_of_images', Types::INTEGER, [ |
||||
|
'notnull' => true, |
||||
|
'default' => 1, |
||||
|
]); |
||||
|
$table->addColumn('user_id', Types::STRING, [ |
||||
|
'notnull' => false, |
||||
|
'length' => 64, |
||||
|
]); |
||||
|
$table->addColumn('app_id', Types::STRING, [ |
||||
|
'notnull' => true, |
||||
|
'length' => 32, |
||||
|
'default' => '', |
||||
|
]); |
||||
|
$table->addColumn('identifier', Types::STRING, [ |
||||
|
'notnull' => false, |
||||
|
'length' => 255, |
||||
|
'default' => '', |
||||
|
]); |
||||
|
$table->addColumn('last_updated', Types::DATETIME, [ |
||||
|
'notnull' => false, |
||||
|
]); |
||||
|
$table->addColumn('completion_expected_at', Types::DATETIME, [ |
||||
|
'notnull' => false, |
||||
|
]); |
||||
|
|
||||
|
$table->setPrimaryKey(['id'], 't2i_tasks_id_index'); |
||||
|
$table->addIndex(['last_updated'], 't2i_tasks_updated'); |
||||
|
$table->addIndex(['status'], 't2i_tasks_status'); |
||||
|
$table->addIndex(['user_id', 'app_id', 'identifier'], 't2i_tasks_uid_appid_ident'); |
||||
|
|
||||
|
return $schema; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
@ -0,0 +1,117 @@ |
|||||
|
<?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\TextToImage\Db; |
||||
|
|
||||
|
use DateTime; |
||||
|
use OCP\AppFramework\Db\Entity; |
||||
|
use OCP\AppFramework\Utility\ITimeFactory; |
||||
|
use OCP\TextToImage\Task as OCPTask; |
||||
|
|
||||
|
/** |
||||
|
* @method setLastUpdated(DateTime $lastUpdated) |
||||
|
* @method DateTime getLastUpdated() |
||||
|
* @method setInput(string $type) |
||||
|
* @method string getInput() |
||||
|
* @method setResultPath(string $resultPath) |
||||
|
* @method string getResultPath() |
||||
|
* @method setStatus(int $type) |
||||
|
* @method int getStatus() |
||||
|
* @method setUserId(?string $userId) |
||||
|
* @method string|null getUserId() |
||||
|
* @method setAppId(string $type) |
||||
|
* @method string getAppId() |
||||
|
* @method setIdentifier(string $identifier) |
||||
|
* @method string|null getIdentifier() |
||||
|
* @method setNumberOfImages(int $numberOfImages) |
||||
|
* @method int getNumberOfImages() |
||||
|
* @method setCompletionExpectedAt(DateTime $at) |
||||
|
* @method DateTime getCompletionExpectedAt() |
||||
|
*/ |
||||
|
class Task extends Entity { |
||||
|
protected $lastUpdated; |
||||
|
protected $type; |
||||
|
protected $input; |
||||
|
protected $status; |
||||
|
protected $userId; |
||||
|
protected $appId; |
||||
|
protected $identifier; |
||||
|
protected $numberOfImages; |
||||
|
protected $completionExpectedAt; |
||||
|
|
||||
|
/** |
||||
|
* @var string[] |
||||
|
*/ |
||||
|
public static array $columns = ['id', 'last_updated', 'input', 'status', 'user_id', 'app_id', 'identifier', 'number_of_images', 'completion_expected_at']; |
||||
|
|
||||
|
/** |
||||
|
* @var string[] |
||||
|
*/ |
||||
|
public static array $fields = ['id', 'lastUpdated', 'input', 'status', 'userId', 'appId', 'identifier', 'numberOfImages', 'completionExpectedAt']; |
||||
|
|
||||
|
|
||||
|
public function __construct() { |
||||
|
// add types in constructor
|
||||
|
$this->addType('id', 'integer'); |
||||
|
$this->addType('lastUpdated', 'datetime'); |
||||
|
$this->addType('input', 'string'); |
||||
|
$this->addType('status', 'integer'); |
||||
|
$this->addType('userId', 'string'); |
||||
|
$this->addType('appId', 'string'); |
||||
|
$this->addType('identifier', 'string'); |
||||
|
$this->addType('numberOfImages', 'integer'); |
||||
|
$this->addType('completionExpectedAt', 'datetime'); |
||||
|
} |
||||
|
|
||||
|
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): Task { |
||||
|
/** @var Task $dbTask */ |
||||
|
$dbTask = Task::fromParams([ |
||||
|
'id' => $task->getId(), |
||||
|
'lastUpdated' => \OCP\Server::get(ITimeFactory::class)->getDateTime(), |
||||
|
'status' => $task->getStatus(), |
||||
|
'numberOfImages' => $task->getNumberOfImages(), |
||||
|
'input' => $task->getInput(), |
||||
|
'userId' => $task->getUserId(), |
||||
|
'appId' => $task->getAppId(), |
||||
|
'identifier' => $task->getIdentifier(), |
||||
|
'completionExpectedAt' => $task->getCompletionExpectedAt(), |
||||
|
]); |
||||
|
return $dbTask; |
||||
|
} |
||||
|
|
||||
|
public function toPublicTask(): OCPTask { |
||||
|
$task = new OCPTask($this->getInput(), $this->getAppId(), $this->getNumberOfImages(), $this->getuserId(), $this->getIdentifier()); |
||||
|
$task->setId($this->getId()); |
||||
|
$task->setStatus($this->getStatus()); |
||||
|
$task->setCompletionExpectedAt($this->getCompletionExpectedAt()); |
||||
|
return $task; |
||||
|
} |
||||
|
} |
@ -0,0 +1,127 @@ |
|||||
|
<?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\TextToImage\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, 'text2image_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 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 $userId |
||||
|
* @param string $appId |
||||
|
* @param string|null $identifier |
||||
|
* @return array |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
public function findUserTasksByApp(?string $userId, string $appId, ?string $identifier = 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 ($identifier !== null) { |
||||
|
$qb->andWhere($qb->expr()->eq('identifier', $qb->createPositionalParameter($identifier))); |
||||
|
} |
||||
|
return $this->findEntities($qb); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param int $timeout |
||||
|
* @return Task[] the deleted tasks |
||||
|
* @throws Exception |
||||
|
*/ |
||||
|
public function deleteOlderThan(int $timeout): array { |
||||
|
$datetime = $this->timeFactory->getDateTime(); |
||||
|
$datetime->sub(new \DateInterval('PT'.$timeout.'S')); |
||||
|
$qb = $this->db->getQueryBuilder(); |
||||
|
$qb->select('*') |
||||
|
->from($this->tableName) |
||||
|
->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($datetime, IQueryBuilder::PARAM_DATE))); |
||||
|
$deletedTasks = $this->findEntities($qb); |
||||
|
$qb = $this->db->getQueryBuilder(); |
||||
|
$qb->delete($this->tableName) |
||||
|
->where($qb->expr()->lt('last_updated', $qb->createPositionalParameter($datetime, IQueryBuilder::PARAM_DATE))); |
||||
|
$qb->executeStatement(); |
||||
|
return $deletedTasks; |
||||
|
} |
||||
|
|
||||
|
public function update(Entity $entity): Entity { |
||||
|
$entity->setLastUpdated($this->timeFactory->getDateTime()); |
||||
|
return parent::update($entity); |
||||
|
} |
||||
|
} |
@ -0,0 +1,334 @@ |
|||||
|
<?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\TextToImage; |
||||
|
|
||||
|
use OC\AppFramework\Bootstrap\Coordinator; |
||||
|
use OC\TextToImage\Db\Task as DbTask; |
||||
|
use OCP\Files\AppData\IAppDataFactory; |
||||
|
use OCP\Files\IAppData; |
||||
|
use OCP\Files\NotFoundException; |
||||
|
use OCP\Files\NotPermittedException; |
||||
|
use OCP\IConfig; |
||||
|
use OCP\TextToImage\Exception\TaskFailureException; |
||||
|
use OCP\TextToImage\Exception\TaskNotFoundException; |
||||
|
use OCP\TextToImage\IManager; |
||||
|
use OCP\TextToImage\Task; |
||||
|
use OC\TextToImage\Db\TaskMapper; |
||||
|
use OCP\AppFramework\Db\DoesNotExistException; |
||||
|
use OCP\AppFramework\Db\MultipleObjectsReturnedException; |
||||
|
use OCP\BackgroundJob\IJobList; |
||||
|
use OCP\DB\Exception; |
||||
|
use OCP\IServerContainer; |
||||
|
use OCP\TextToImage\IProvider; |
||||
|
use OCP\PreConditionNotMetException; |
||||
|
use Psr\Log\LoggerInterface; |
||||
|
use RuntimeException; |
||||
|
use Throwable; |
||||
|
|
||||
|
class Manager implements IManager { |
||||
|
/** @var ?IProvider[] */ |
||||
|
private ?array $providers = null; |
||||
|
private IAppData $appData; |
||||
|
|
||||
|
public function __construct( |
||||
|
private IServerContainer $serverContainer, |
||||
|
private Coordinator $coordinator, |
||||
|
private LoggerInterface $logger, |
||||
|
private IJobList $jobList, |
||||
|
private TaskMapper $taskMapper, |
||||
|
private IConfig $config, |
||||
|
IAppDataFactory $appDataFactory, |
||||
|
) { |
||||
|
$this->appData = $appDataFactory->get('core'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @inerhitDocs |
||||
|
*/ |
||||
|
public function getProviders(): array { |
||||
|
$context = $this->coordinator->getRegistrationContext(); |
||||
|
if ($context === null) { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
if ($this->providers !== null) { |
||||
|
return $this->providers; |
||||
|
} |
||||
|
|
||||
|
$this->providers = []; |
||||
|
|
||||
|
foreach ($context->getTextToImageProviders() as $providerServiceRegistration) { |
||||
|
$class = $providerServiceRegistration->getService(); |
||||
|
try { |
||||
|
$this->providers[$class] = $this->serverContainer->get($class); |
||||
|
} catch (Throwable $e) { |
||||
|
$this->logger->error('Failed to load Text to image provider ' . $class, [ |
||||
|
'exception' => $e, |
||||
|
]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return $this->providers; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @inheritDoc |
||||
|
*/ |
||||
|
public function hasProviders(): bool { |
||||
|
$context = $this->coordinator->getRegistrationContext(); |
||||
|
if ($context === null) { |
||||
|
return false; |
||||
|
} |
||||
|
return count($context->getTextToImageProviders()) > 0; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @inheritDoc |
||||
|
*/ |
||||
|
public function runTask(Task $task): void { |
||||
|
$this->logger->debug('Running TextToImage Task'); |
||||
|
if (!$this->hasProviders()) { |
||||
|
throw new PreConditionNotMetException('No text to image provider is installed that can handle this task'); |
||||
|
} |
||||
|
$providers = $this->getProviders(); |
||||
|
|
||||
|
$json = $this->config->getAppValue('core', 'ai.text2image_provider', ''); |
||||
|
if ($json !== '') { |
||||
|
try { |
||||
|
$className = json_decode($json, true, 512, JSON_THROW_ON_ERROR); |
||||
|
$provider = current(array_filter($providers, fn ($provider) => $provider::class === $className)); |
||||
|
if ($provider !== false) { |
||||
|
$providers = [$provider]; |
||||
|
} |
||||
|
} catch (\JsonException $e) { |
||||
|
$this->logger->warning('Failed to decode Text2Image setting `ai.text2image_provider`', ['exception' => $e]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
foreach ($providers as $provider) { |
||||
|
$this->logger->debug('Trying to run Text2Image provider '.$provider::class); |
||||
|
try { |
||||
|
$task->setStatus(Task::STATUS_RUNNING); |
||||
|
if ($task->getId() === null) { |
||||
|
$this->logger->debug('Inserting Text2Image task into DB'); |
||||
|
$taskEntity = $this->taskMapper->insert(DbTask::fromPublicTask($task)); |
||||
|
$task->setId($taskEntity->getId()); |
||||
|
} else { |
||||
|
$this->logger->debug('Updating Text2Image task in DB'); |
||||
|
$this->taskMapper->update(DbTask::fromPublicTask($task)); |
||||
|
} |
||||
|
try { |
||||
|
$folder = $this->appData->getFolder('text2image'); |
||||
|
} catch(NotFoundException) { |
||||
|
$this->logger->debug('Creating folder in appdata for Text2Image results'); |
||||
|
$folder = $this->appData->newFolder('text2image'); |
||||
|
} |
||||
|
try { |
||||
|
$folder = $folder->getFolder((string) $task->getId()); |
||||
|
} catch(NotFoundException) { |
||||
|
$this->logger->debug('Creating new folder in appdata Text2Image results folder'); |
||||
|
$folder = $folder->newFolder((string) $task->getId()); |
||||
|
} |
||||
|
$this->logger->debug('Creating result files for Text2Image task'); |
||||
|
$resources = []; |
||||
|
$files = []; |
||||
|
for ($i = 0; $i < $task->getNumberOfImages(); $i++) { |
||||
|
$file = $folder->newFile((string) $i); |
||||
|
$files[] = $file; |
||||
|
$resource = $file->write(); |
||||
|
if ($resource !== false && $resource !== true && is_resource($resource)) { |
||||
|
$resources[] = $resource; |
||||
|
} else { |
||||
|
throw new RuntimeException('Text2Image generation using provider "' . $provider->getName() . '" failed: Couldn\'t open file to write.'); |
||||
|
} |
||||
|
} |
||||
|
$this->logger->debug('Calling Text2Image provider\'s generate method'); |
||||
|
$provider->generate($task->getInput(), $resources); |
||||
|
for ($i = 0; $i < $task->getNumberOfImages(); $i++) { |
||||
|
if (is_resource($resources[$i])) { |
||||
|
// If $resource hasn't been closed yet, we'll do that here
|
||||
|
fclose($resources[$i]); |
||||
|
} |
||||
|
} |
||||
|
$task->setStatus(Task::STATUS_SUCCESSFUL); |
||||
|
$this->logger->debug('Updating Text2Image task in DB'); |
||||
|
$this->taskMapper->update(DbTask::fromPublicTask($task)); |
||||
|
return; |
||||
|
} catch (\RuntimeException|\Throwable $e) { |
||||
|
for ($i = 0; $i < $task->getNumberOfImages(); $i++) { |
||||
|
if (isset($files, $files[$i])) { |
||||
|
try { |
||||
|
$files[$i]->delete(); |
||||
|
} catch(NotPermittedException $e) { |
||||
|
$this->logger->warning('Failed to clean up Text2Image result file after error', ['exception' => $e]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$this->logger->info('Text2Image generation using provider "' . $provider->getName() . '" failed', ['exception' => $e]); |
||||
|
$task->setStatus(Task::STATUS_FAILED); |
||||
|
try { |
||||
|
$this->taskMapper->update(DbTask::fromPublicTask($task)); |
||||
|
} catch (Exception $e) { |
||||
|
$this->logger->warning('Failed to update database after Text2Image error', ['exception' => $e]); |
||||
|
} |
||||
|
throw new TaskFailureException('Text2Image generation using provider "' . $provider->getName() . '" failed: ' . $e->getMessage(), 0, $e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
$task->setStatus(Task::STATUS_FAILED); |
||||
|
try { |
||||
|
$this->taskMapper->update(DbTask::fromPublicTask($task)); |
||||
|
} catch (Exception $e) { |
||||
|
$this->logger->warning('Failed to update database after Text2Image error', ['exception' => $e]); |
||||
|
} |
||||
|
throw new TaskFailureException('Could not run task'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @inheritDoc |
||||
|
*/ |
||||
|
public function scheduleTask(Task $task): void { |
||||
|
if (!$this->hasProviders()) { |
||||
|
throw new PreConditionNotMetException('No text to image provider is installed that can handle this task'); |
||||
|
} |
||||
|
$this->logger->debug('Scheduling Text2Image Task'); |
||||
|
$task->setStatus(Task::STATUS_SCHEDULED); |
||||
|
$taskEntity = DbTask::fromPublicTask($task); |
||||
|
$this->taskMapper->insert($taskEntity); |
||||
|
$task->setId($taskEntity->getId()); |
||||
|
$this->jobList->add(TaskBackgroundJob::class, [ |
||||
|
'taskId' => $task->getId() |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @inheritDoc |
||||
|
*/ |
||||
|
public function runOrScheduleTask(Task $task) : void { |
||||
|
if (!$this->hasProviders()) { |
||||
|
throw new PreConditionNotMetException('No text to image provider is installed that can handle this task'); |
||||
|
} |
||||
|
$providers = $this->getProviders(); |
||||
|
|
||||
|
$json = $this->config->getAppValue('core', 'ai.text2image_provider', ''); |
||||
|
if ($json !== '') { |
||||
|
try { |
||||
|
$id = json_decode($json, true, 512, JSON_THROW_ON_ERROR); |
||||
|
$provider = current(array_filter($providers, fn ($provider) => $provider->getId() === $id)); |
||||
|
if ($provider !== false) { |
||||
|
$providers = [$provider]; |
||||
|
} |
||||
|
} catch (\JsonException $e) { |
||||
|
$this->logger->warning('Failed to decode Text2Image setting `ai.text2image_provider`', ['exception' => $e]); |
||||
|
} |
||||
|
} |
||||
|
$maxExecutionTime = (int) ini_get('max_execution_time'); |
||||
|
// Offload the tttttttask to a background job if the expected runtime of the likely provider is longer than 80% of our max execution time
|
||||
|
if ($providers[0]->getExpectedRuntime() > $maxExecutionTime * 0.8) { |
||||
|
$this->scheduleTask($task); |
||||
|
return; |
||||
|
} |
||||
|
$this->runTask($task); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @inheritDoc |
||||
|
*/ |
||||
|
public function deleteTask(Task $task): void { |
||||
|
$taskEntity = DbTask::fromPublicTask($task); |
||||
|
$this->taskMapper->delete($taskEntity); |
||||
|
$this->jobList->remove(TaskBackgroundJob::class, [ |
||||
|
'taskId' => $task->getId() |
||||
|
]); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get a task from its id |
||||
|
* |
||||
|
* @param int $id The id of the task |
||||
|
* @return Task |
||||
|
* @throws RuntimeException If the query failed |
||||
|
* @throws TaskNotFoundException If the task could not be found |
||||
|
*/ |
||||
|
public function getTask(int $id): Task { |
||||
|
try { |
||||
|
$taskEntity = $this->taskMapper->find($id); |
||||
|
return $taskEntity->toPublicTask(); |
||||
|
} catch (DoesNotExistException $e) { |
||||
|
throw new TaskNotFoundException('Could not find task with the provided id'); |
||||
|
} catch (MultipleObjectsReturnedException $e) { |
||||
|
throw new RuntimeException('Could not uniquely identify task with given id', 0, $e); |
||||
|
} catch (Exception $e) { |
||||
|
throw new RuntimeException('Failure while trying to find task by id: ' . $e->getMessage(), 0, $e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get a task from its user id and task id |
||||
|
* If userId is null, this can only get a task that was scheduled anonymously |
||||
|
* |
||||
|
* @param int $id The id of the task |
||||
|
* @param string|null $userId The user id that scheduled the task |
||||
|
* @return Task |
||||
|
* @throws RuntimeException If the query failed |
||||
|
* @throws TaskNotFoundException If the task could not be found |
||||
|
*/ |
||||
|
public function getUserTask(int $id, ?string $userId): Task { |
||||
|
try { |
||||
|
$taskEntity = $this->taskMapper->findByIdAndUser($id, $userId); |
||||
|
return $taskEntity->toPublicTask(); |
||||
|
} catch (DoesNotExistException $e) { |
||||
|
throw new TaskNotFoundException('Could not find task with the provided id and user id'); |
||||
|
} catch (MultipleObjectsReturnedException $e) { |
||||
|
throw new RuntimeException('Could not uniquely identify task with given id and user id', 0, $e); |
||||
|
} catch (Exception $e) { |
||||
|
throw new RuntimeException('Failure while trying to find task by id and user id: ' . $e->getMessage(), 0, $e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get a list of tasks scheduled by a specific user for a specific app |
||||
|
* and optionally with a specific identifier. |
||||
|
* This cannot be used to get anonymously scheduled tasks |
||||
|
* |
||||
|
* @param string $userId |
||||
|
* @param string $appId |
||||
|
* @param string|null $identifier |
||||
|
* @return Task[] |
||||
|
* @throws RuntimeException |
||||
|
*/ |
||||
|
public function getUserTasksByApp(?string $userId, string $appId, ?string $identifier = null): array { |
||||
|
try { |
||||
|
$taskEntities = $this->taskMapper->findUserTasksByApp($userId, $appId, $identifier); |
||||
|
return array_map(static function (DbTask $taskEntity) { |
||||
|
return $taskEntity->toPublicTask(); |
||||
|
}, $taskEntities); |
||||
|
} catch (Exception $e) { |
||||
|
throw new RuntimeException('Failure while trying to find tasks by appId and identifier: ' . $e->getMessage(), 0, $e); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,78 @@ |
|||||
|
<?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\TextToImage; |
||||
|
|
||||
|
use OC\TextToImage\Db\TaskMapper; |
||||
|
use OCP\AppFramework\Utility\ITimeFactory; |
||||
|
use OCP\BackgroundJob\TimedJob; |
||||
|
use OCP\DB\Exception; |
||||
|
use OCP\Files\AppData\IAppDataFactory; |
||||
|
use OCP\Files\IAppData; |
||||
|
use OCP\Files\NotFoundException; |
||||
|
use OCP\Files\NotPermittedException; |
||||
|
use Psr\Log\LoggerInterface; |
||||
|
|
||||
|
class RemoveOldTasksBackgroundJob extends TimedJob { |
||||
|
public const MAX_TASK_AGE_SECONDS = 60 * 50 * 24 * 7; // 1 week
|
||||
|
|
||||
|
private IAppData $appData; |
||||
|
|
||||
|
public function __construct( |
||||
|
ITimeFactory $timeFactory, |
||||
|
private TaskMapper $taskMapper, |
||||
|
private LoggerInterface $logger, |
||||
|
IAppDataFactory $appDataFactory, |
||||
|
) { |
||||
|
parent::__construct($timeFactory); |
||||
|
$this->appData = $appDataFactory->get('core'); |
||||
|
$this->setInterval(60 * 60 * 24); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param mixed $argument |
||||
|
* @inheritDoc |
||||
|
*/ |
||||
|
protected function run($argument) { |
||||
|
try { |
||||
|
$deletedTasks = $this->taskMapper->deleteOlderThan(self::MAX_TASK_AGE_SECONDS); |
||||
|
$folder = $this->appData->getFolder('text2image'); |
||||
|
foreach ($deletedTasks as $deletedTask) { |
||||
|
try { |
||||
|
$folder->getFolder((string)$deletedTask->getId())->delete(); |
||||
|
} catch (NotFoundException) { |
||||
|
// noop
|
||||
|
} catch (NotPermittedException $e) { |
||||
|
$this->logger->warning('Failed to delete stale text to image task files', ['exception' => $e]); |
||||
|
} |
||||
|
} |
||||
|
} catch (Exception $e) { |
||||
|
$this->logger->warning('Failed to delete stale text to image tasks', ['exception' => $e]); |
||||
|
} catch(NotFoundException) { |
||||
|
// noop
|
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,63 @@ |
|||||
|
<?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\TextToImage; |
||||
|
|
||||
|
use OCP\AppFramework\Utility\ITimeFactory; |
||||
|
use OCP\BackgroundJob\QueuedJob; |
||||
|
use OCP\EventDispatcher\IEventDispatcher; |
||||
|
use OCP\TextToImage\Events\TaskFailedEvent; |
||||
|
use OCP\TextToImage\Events\TaskSuccessfulEvent; |
||||
|
use OCP\TextToImage\IManager; |
||||
|
|
||||
|
class TaskBackgroundJob extends QueuedJob { |
||||
|
public function __construct( |
||||
|
ITimeFactory $timeFactory, |
||||
|
private IManager $text2imageManager, |
||||
|
private IEventDispatcher $eventDispatcher, |
||||
|
) { |
||||
|
parent::__construct($timeFactory); |
||||
|
// We want to avoid overloading the machine with these jobs
|
||||
|
// so we only allow running one job at a time
|
||||
|
$this->setAllowParallelRuns(false); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param array{taskId: int} $argument |
||||
|
* @inheritDoc |
||||
|
*/ |
||||
|
protected function run($argument) { |
||||
|
$taskId = $argument['taskId']; |
||||
|
$task = $this->text2imageManager->getTask($taskId); |
||||
|
try { |
||||
|
$this->text2imageManager->runTask($task); |
||||
|
$event = new TaskSuccessfulEvent($task); |
||||
|
} catch (\Throwable $e) { |
||||
|
$event = new TaskFailedEvent($task, $e->getMessage()); |
||||
|
} |
||||
|
$this->eventDispatcher->dispatchTyped($event); |
||||
|
} |
||||
|
} |
@ -0,0 +1,52 @@ |
|||||
|
<?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\TextToImage\Events; |
||||
|
|
||||
|
use OCP\EventDispatcher\Event; |
||||
|
use OCP\TextToImage\Task; |
||||
|
|
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
abstract class AbstractTextToImageEvent extends Event { |
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function __construct( |
||||
|
private Task $task |
||||
|
) { |
||||
|
parent::__construct(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return Task |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function getTask(): Task { |
||||
|
return $this->task; |
||||
|
} |
||||
|
} |
@ -0,0 +1,54 @@ |
|||||
|
<?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\TextToImage\Events; |
||||
|
|
||||
|
use OCP\TextToImage\Task; |
||||
|
|
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
class TaskFailedEvent extends AbstractTextToImageEvent { |
||||
|
/** |
||||
|
* @param Task $task |
||||
|
* @param string $errorMessage |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function __construct( |
||||
|
Task $task, |
||||
|
private string $errorMessage, |
||||
|
) { |
||||
|
parent::__construct($task); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return string |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function getErrorMessage(): string { |
||||
|
return $this->errorMessage; |
||||
|
} |
||||
|
} |
@ -0,0 +1,33 @@ |
|||||
|
<?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\TextToImage\Events; |
||||
|
|
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
class TaskSuccessfulEvent extends AbstractTextToImageEvent { |
||||
|
} |
@ -0,0 +1,31 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/** |
||||
|
* @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\TextToImage\Exception; |
||||
|
|
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
class TaskFailureException extends TextToImageException { |
||||
|
} |
@ -0,0 +1,31 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/** |
||||
|
* @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\TextToImage\Exception; |
||||
|
|
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
class TaskNotFoundException extends TextToImageException { |
||||
|
} |
@ -0,0 +1,31 @@ |
|||||
|
<?php |
||||
|
|
||||
|
/** |
||||
|
* @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\TextToImage\Exception; |
||||
|
|
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
class TextToImageException extends \Exception { |
||||
|
} |
@ -0,0 +1,116 @@ |
|||||
|
<?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\TextToImage; |
||||
|
|
||||
|
use OCP\DB\Exception; |
||||
|
use OCP\PreConditionNotMetException; |
||||
|
use OCP\TextToImage\Exception\TaskFailureException; |
||||
|
use OCP\TextToImage\Exception\TaskNotFoundException; |
||||
|
use RuntimeException; |
||||
|
|
||||
|
/** |
||||
|
* API surface for apps interacting with and making use of TextToImage providers |
||||
|
* without knowing which providers are installed |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
interface IManager { |
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function hasProviders(): bool; |
||||
|
|
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
* @return IProvider[] |
||||
|
*/ |
||||
|
public function getProviders(): 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 TaskFailureException If something else failed. When this is thrown task status was already set to failure. |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function runTask(Task $task): void; |
||||
|
|
||||
|
/** |
||||
|
* Will schedule a TextToImage process in the background. The result will become available |
||||
|
* with the \OCP\TextToImage\TaskSuccessfulEvent |
||||
|
* If inference fails a \OCP\TextToImage\Events\TaskFailedEvent will be dispatched instead |
||||
|
* |
||||
|
* @param Task $task The task to schedule |
||||
|
* @throws PreConditionNotMetException If no provider was registered but this method was still called |
||||
|
* @throws Exception If there was a problem inserting the task into the database |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function scheduleTask(Task $task) : void; |
||||
|
|
||||
|
/** |
||||
|
* @throws Exception if there was a problem inserting the task into the database |
||||
|
* @throws PreConditionNotMetException if no provider is registered |
||||
|
* @throws TaskFailureException If the task run failed |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function runOrScheduleTask(Task $task) : void; |
||||
|
|
||||
|
/** |
||||
|
* Delete a task that has been scheduled before |
||||
|
* |
||||
|
* @param Task $task The task to delete |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function deleteTask(Task $task): void; |
||||
|
|
||||
|
/** |
||||
|
* @param int $id The id of the task |
||||
|
* @return Task |
||||
|
* @throws RuntimeException If the query failed |
||||
|
* @throws TaskNotFoundException If the task could not be found |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function getTask(int $id): Task; |
||||
|
|
||||
|
/** |
||||
|
* @param int $id The id of the task |
||||
|
* @param string|null $userId The user id that scheduled the task |
||||
|
* @return Task |
||||
|
* @throws RuntimeException If the query failed |
||||
|
* @throws TaskNotFoundException If the task could not be found |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function getUserTask(int $id, ?string $userId): Task; |
||||
|
|
||||
|
/** |
||||
|
* @param ?string $userId |
||||
|
* @param string $appId |
||||
|
* @param string|null $identifier |
||||
|
* @return Task[] |
||||
|
* @since 28.0.0 |
||||
|
* @throws RuntimeException If the query failed |
||||
|
*/ |
||||
|
public function getUserTasksByApp(?string $userId, string $appId, ?string $identifier = null): array; |
||||
|
} |
@ -0,0 +1,64 @@ |
|||||
|
<?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\TextToImage; |
||||
|
|
||||
|
use RuntimeException; |
||||
|
|
||||
|
/** |
||||
|
* This is the interface that is implemented by apps that |
||||
|
* implement a text to image provider |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
interface IProvider { |
||||
|
/** |
||||
|
* An arbitrary unique text string identifying this provider |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function getId(): string; |
||||
|
|
||||
|
/** |
||||
|
* The localized name of this provider |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function getName(): string; |
||||
|
|
||||
|
/** |
||||
|
* Processes a text |
||||
|
* |
||||
|
* @param string $prompt The input text |
||||
|
* @param resource[] $resources The file resources to write the images to |
||||
|
* @return void |
||||
|
* @since 28.0.0 |
||||
|
* @throws RuntimeException If the text could not be processed |
||||
|
*/ |
||||
|
public function generate(string $prompt, array $resources): void; |
||||
|
|
||||
|
/** |
||||
|
* The expected runtime for one task with this provider in seconds |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function getExpectedRuntime(): int; |
||||
|
} |
@ -0,0 +1,212 @@ |
|||||
|
<?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\TextToImage; |
||||
|
|
||||
|
use DateTime; |
||||
|
use OCP\Files\AppData\IAppDataFactory; |
||||
|
use OCP\Files\NotFoundException; |
||||
|
use OCP\Files\NotPermittedException; |
||||
|
use OCP\IImage; |
||||
|
use OCP\Image; |
||||
|
|
||||
|
/** |
||||
|
* This is a text to image task |
||||
|
* |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final class Task implements \JsonSerializable { |
||||
|
protected ?int $id = null; |
||||
|
|
||||
|
protected ?DateTime $completionExpectedAt = null; |
||||
|
|
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public const STATUS_FAILED = 4; |
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public const STATUS_SUCCESSFUL = 3; |
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public const STATUS_RUNNING = 2; |
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public const STATUS_SCHEDULED = 1; |
||||
|
/** |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public const STATUS_UNKNOWN = 0; |
||||
|
|
||||
|
/** |
||||
|
* @psalm-var self::STATUS_* |
||||
|
*/ |
||||
|
protected int $status = self::STATUS_UNKNOWN; |
||||
|
|
||||
|
/** |
||||
|
* @param string $input |
||||
|
* @param string $appId |
||||
|
* @param int $numberOfImages |
||||
|
* @param string|null $userId |
||||
|
* @param null|string $identifier An arbitrary identifier for this task. max length: 255 chars |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function __construct( |
||||
|
protected string $input, |
||||
|
protected string $appId, |
||||
|
protected int $numberOfImages, |
||||
|
protected ?string $userId, |
||||
|
protected ?string $identifier = '', |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return IImage[]|null |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function getOutputImages(): ?array { |
||||
|
$appData = \OCP\Server::get(IAppDataFactory::class)->get('core'); |
||||
|
try { |
||||
|
$folder = $appData->getFolder('text2image')->getFolder((string)$this->getId()); |
||||
|
$images = []; |
||||
|
for ($i = 0; $i < $this->getNumberOfImages(); $i++) { |
||||
|
$image = new Image(); |
||||
|
$image->loadFromFileHandle($folder->getFile((string) $i)->read()); |
||||
|
$images[] = $image; |
||||
|
} |
||||
|
return $images; |
||||
|
} catch (NotFoundException|NotPermittedException) { |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return int |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function getNumberOfImages(): int { |
||||
|
return $this->numberOfImages; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @psalm-return self::STATUS_* |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function getStatus(): int { |
||||
|
return $this->status; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @psalm-param self::STATUS_* $status |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function setStatus(int $status): void { |
||||
|
$this->status = $status; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param ?DateTime $at |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function setCompletionExpectedAt(?DateTime $at): void { |
||||
|
$this->completionExpectedAt = $at; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return ?DateTime |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function getCompletionExpectedAt(): ?DateTime { |
||||
|
return $this->completionExpectedAt; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return int|null |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function getId(): ?int { |
||||
|
return $this->id; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @param int|null $id |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function setId(?int $id): void { |
||||
|
$this->id = $id; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return string |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function getInput(): string { |
||||
|
return $this->input; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return string |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function getAppId(): string { |
||||
|
return $this->appId; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return null|string |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function getIdentifier(): ?string { |
||||
|
return $this->identifier; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return string|null |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
final public function getUserId(): ?string { |
||||
|
return $this->userId; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @psalm-return array{id: ?int, status: self::STATUS_*, userId: ?string, appId: string, input: string, identifier: ?string, numberOfImages: int, completionExpectedAt: ?int} |
||||
|
* @since 28.0.0 |
||||
|
*/ |
||||
|
public function jsonSerialize(): array { |
||||
|
return [ |
||||
|
'id' => $this->getId(), |
||||
|
'status' => $this->getStatus(), |
||||
|
'userId' => $this->getUserId(), |
||||
|
'appId' => $this->getAppId(), |
||||
|
'numberOfImages' => $this->getNumberOfImages(), |
||||
|
'input' => $this->getInput(), |
||||
|
'identifier' => $this->getIdentifier(), |
||||
|
'completionExpectedAt' => $this->getCompletionExpectedAt()->getTimestamp(), |
||||
|
]; |
||||
|
} |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue