You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

235 lines
8.2 KiB

  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OC\Core\Controller;
  8. use OC\Core\ResponseDefinitions;
  9. use OC\Files\AppData\AppData;
  10. use OCP\AppFramework\Http;
  11. use OCP\AppFramework\Http\Attribute\AnonRateLimit;
  12. use OCP\AppFramework\Http\Attribute\ApiRoute;
  13. use OCP\AppFramework\Http\Attribute\BruteForceProtection;
  14. use OCP\AppFramework\Http\Attribute\NoAdminRequired;
  15. use OCP\AppFramework\Http\Attribute\UserRateLimit;
  16. use OCP\AppFramework\Http\DataResponse;
  17. use OCP\AppFramework\Http\FileDisplayResponse;
  18. use OCP\AppFramework\OCSController;
  19. use OCP\DB\Exception;
  20. use OCP\Files\NotFoundException;
  21. use OCP\IL10N;
  22. use OCP\IRequest;
  23. use OCP\PreConditionNotMetException;
  24. use OCP\TextToImage\Exception\TaskFailureException;
  25. use OCP\TextToImage\Exception\TaskNotFoundException;
  26. use OCP\TextToImage\IManager;
  27. use OCP\TextToImage\Task;
  28. /**
  29. * @psalm-import-type CoreTextToImageTask from ResponseDefinitions
  30. */
  31. class TextToImageApiController extends OCSController {
  32. public function __construct(
  33. string $appName,
  34. IRequest $request,
  35. private IManager $textToImageManager,
  36. private IL10N $l,
  37. private ?string $userId,
  38. private AppData $appData,
  39. ) {
  40. parent::__construct($appName, $request);
  41. }
  42. /**
  43. * Check whether this feature is available
  44. *
  45. * @return DataResponse<Http::STATUS_OK, array{isAvailable: bool}, array{}>
  46. *
  47. * 200: Returns availability status
  48. */
  49. #[NoAdminRequired]
  50. #[ApiRoute(verb: 'GET', url: '/is_available', root: '/text2image')]
  51. public function isAvailable(): DataResponse {
  52. return new DataResponse([
  53. 'isAvailable' => $this->textToImageManager->hasProviders(),
  54. ]);
  55. }
  56. /**
  57. * This endpoint allows scheduling a text to image task
  58. *
  59. * @param string $input Input text
  60. * @param string $appId ID of the app that will execute the task
  61. * @param string $identifier An arbitrary identifier for the task
  62. * @param int $numberOfImages The number of images to generate
  63. *
  64. * @return DataResponse<Http::STATUS_OK, array{task: CoreTextToImageTask}, array{}>|DataResponse<Http::STATUS_PRECONDITION_FAILED|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  65. *
  66. * 200: Task scheduled successfully
  67. * 412: Scheduling task is not possible
  68. */
  69. #[NoAdminRequired]
  70. #[UserRateLimit(limit: 20, period: 120)]
  71. #[ApiRoute(verb: 'POST', url: '/schedule', root: '/text2image')]
  72. public function schedule(string $input, string $appId, string $identifier = '', int $numberOfImages = 8): DataResponse {
  73. $task = new Task($input, $appId, $numberOfImages, $this->userId, $identifier);
  74. try {
  75. try {
  76. $this->textToImageManager->runOrScheduleTask($task);
  77. } catch (TaskFailureException) {
  78. // Task status was already updated by the manager, nothing to do here
  79. }
  80. $json = $task->jsonSerialize();
  81. return new DataResponse([
  82. 'task' => $json,
  83. ]);
  84. } catch (PreConditionNotMetException) {
  85. return new DataResponse(['message' => $this->l->t('No text to image provider is available')], Http::STATUS_PRECONDITION_FAILED);
  86. } catch (Exception) {
  87. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  88. }
  89. }
  90. /**
  91. * This endpoint allows checking the status and results of a task.
  92. * Tasks are removed 1 week after receiving their last update.
  93. *
  94. * @param int $id The id of the task
  95. *
  96. * @return DataResponse<Http::STATUS_OK, array{task: CoreTextToImageTask}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  97. *
  98. * 200: Task returned
  99. * 404: Task not found
  100. */
  101. #[NoAdminRequired]
  102. #[BruteForceProtection(action: 'text2image')]
  103. #[ApiRoute(verb: 'GET', url: '/task/{id}', root: '/text2image')]
  104. public function getTask(int $id): DataResponse {
  105. try {
  106. $task = $this->textToImageManager->getUserTask($id, $this->userId);
  107. $json = $task->jsonSerialize();
  108. return new DataResponse([
  109. 'task' => $json,
  110. ]);
  111. } catch (TaskNotFoundException) {
  112. $res = new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
  113. $res->throttle(['action' => 'text2image']);
  114. return $res;
  115. } catch (\RuntimeException) {
  116. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  117. }
  118. }
  119. /**
  120. * This endpoint allows downloading the resulting image of a task
  121. *
  122. * @param int $id The id of the task
  123. * @param int $index The index of the image to retrieve
  124. *
  125. * @return FileDisplayResponse<Http::STATUS_OK, array{'Content-Type': string}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  126. *
  127. * 200: Image returned
  128. * 404: Task or image not found
  129. */
  130. #[NoAdminRequired]
  131. #[BruteForceProtection(action: 'text2image')]
  132. #[ApiRoute(verb: 'GET', url: '/task/{id}/image/{index}', root: '/text2image')]
  133. public function getImage(int $id, int $index): DataResponse|FileDisplayResponse {
  134. try {
  135. $task = $this->textToImageManager->getUserTask($id, $this->userId);
  136. try {
  137. $folder = $this->appData->getFolder('text2image');
  138. } catch (NotFoundException) {
  139. $res = new DataResponse(['message' => $this->l->t('Image not found')], Http::STATUS_NOT_FOUND);
  140. $res->throttle(['action' => 'text2image']);
  141. return $res;
  142. }
  143. $file = $folder->getFolder((string)$task->getId())->getFile((string)$index);
  144. $info = getimagesizefromstring($file->getContent());
  145. return new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => image_type_to_mime_type($info[2])]);
  146. } catch (TaskNotFoundException) {
  147. $res = new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
  148. $res->throttle(['action' => 'text2image']);
  149. return $res;
  150. } catch (\RuntimeException) {
  151. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  152. } catch (NotFoundException) {
  153. $res = new DataResponse(['message' => $this->l->t('Image not found')], Http::STATUS_NOT_FOUND);
  154. $res->throttle(['action' => 'text2image']);
  155. return $res;
  156. }
  157. }
  158. /**
  159. * This endpoint allows to delete a scheduled task for a user
  160. *
  161. * @param int $id The id of the task
  162. *
  163. * @return DataResponse<Http::STATUS_OK, array{task: CoreTextToImageTask}, array{}>|DataResponse<Http::STATUS_NOT_FOUND|Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  164. *
  165. * 200: Task returned
  166. * 404: Task not found
  167. */
  168. #[NoAdminRequired]
  169. #[BruteForceProtection(action: 'text2image')]
  170. #[ApiRoute(verb: 'DELETE', url: '/task/{id}', root: '/text2image')]
  171. public function deleteTask(int $id): DataResponse {
  172. try {
  173. $task = $this->textToImageManager->getUserTask($id, $this->userId);
  174. $this->textToImageManager->deleteTask($task);
  175. $json = $task->jsonSerialize();
  176. return new DataResponse([
  177. 'task' => $json,
  178. ]);
  179. } catch (TaskNotFoundException) {
  180. $res = new DataResponse(['message' => $this->l->t('Task not found')], Http::STATUS_NOT_FOUND);
  181. $res->throttle(['action' => 'text2image']);
  182. return $res;
  183. } catch (\RuntimeException) {
  184. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  185. }
  186. }
  187. /**
  188. * This endpoint returns a list of tasks of a user that are related
  189. * with a specific appId and optionally with an identifier
  190. *
  191. * @param string $appId ID of the app
  192. * @param string|null $identifier An arbitrary identifier for the task
  193. * @return DataResponse<Http::STATUS_OK, array{tasks: list<CoreTextToImageTask>}, array{}>|DataResponse<Http::STATUS_INTERNAL_SERVER_ERROR, array{message: string}, array{}>
  194. *
  195. * 200: Task list returned
  196. */
  197. #[NoAdminRequired]
  198. #[AnonRateLimit(limit: 5, period: 120)]
  199. #[ApiRoute(verb: 'GET', url: '/tasks/app/{appId}', root: '/text2image')]
  200. public function listTasksByApp(string $appId, ?string $identifier = null): DataResponse {
  201. try {
  202. $tasks = $this->textToImageManager->getUserTasksByApp($this->userId, $appId, $identifier);
  203. $json = array_values(array_map(static function (Task $task) {
  204. return $task->jsonSerialize();
  205. }, $tasks));
  206. return new DataResponse([
  207. 'tasks' => $json,
  208. ]);
  209. } catch (\RuntimeException) {
  210. return new DataResponse(['message' => $this->l->t('Internal error')], Http::STATUS_INTERNAL_SERVER_ERROR);
  211. }
  212. }
  213. }