From 181f819e417a1818f37200f9071fa632c82a0fc2 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 3 Nov 2023 16:22:54 +0100 Subject: [PATCH 01/14] enh(TextProcessing): Add IProvider2 - allow providers to obtain current task's userId - allow providers to expose average task runtime Signed-off-by: Marcel Klehr --- .../TextProcessingApiController.php | 11 ++- .../Version28000Date20231103104802.php | 60 ++++++++++++++++ core/ResponseDefinitions.php | 1 + lib/private/TextProcessing/Db/Task.php | 10 ++- lib/private/TextProcessing/Manager.php | 69 +++++++++++++++---- lib/public/TextProcessing/IManager.php | 16 +++++ lib/public/TextProcessing/IProvider2.php | 48 +++++++++++++ lib/public/TextProcessing/Task.php | 19 ++++- 8 files changed, 213 insertions(+), 21 deletions(-) create mode 100644 core/Migrations/Version28000Date20231103104802.php create mode 100644 lib/public/TextProcessing/IProvider2.php diff --git a/core/Controller/TextProcessingApiController.php b/core/Controller/TextProcessingApiController.php index 8b5175f0e5c77..6b975a7ed61ca 100644 --- a/core/Controller/TextProcessingApiController.php +++ b/core/Controller/TextProcessingApiController.php @@ -35,6 +35,7 @@ use OCP\AppFramework\Http\Attribute\UserRateLimit; use OCP\AppFramework\Http\DataResponse; use OCP\Common\Exception\NotFoundException; +use OCP\DB\Exception; use OCP\IL10N; use OCP\IRequest; use OCP\TextProcessing\ITaskType; @@ -102,7 +103,7 @@ public function taskTypes(): DataResponse { * @param string $appId ID of the app that will execute the task * @param string $identifier An arbitrary identifier for the task * - * @return DataResponse|DataResponse + * @return DataResponse|DataResponse * * 200: Task scheduled successfully * 400: Scheduling task is not possible @@ -118,7 +119,11 @@ public function schedule(string $input, string $type, string $appId, string $ide return new DataResponse(['message' => $this->l->t('Requested task type does not exist')], Http::STATUS_BAD_REQUEST); } try { - $this->textProcessingManager->scheduleTask($task); + try { + $this->textProcessingManager->runOrScheduleTask($task); + } catch(\RuntimeException) { + // noop, because the task object has the failure status set already, we just return the task json + } $json = $task->jsonSerialize(); @@ -127,6 +132,8 @@ public function schedule(string $input, string $type, string $appId, string $ide ]); } catch (PreConditionNotMetException) { return new DataResponse(['message' => $this->l->t('Necessary language model provider is not available')], Http::STATUS_PRECONDITION_FAILED); + } catch (Exception) { + return new DataResponse(['message' => 'Internal server error'], Http::STATUS_INTERNAL_SERVER_ERROR); } } diff --git a/core/Migrations/Version28000Date20231103104802.php b/core/Migrations/Version28000Date20231103104802.php new file mode 100644 index 0000000000000..69dddbccee93f --- /dev/null +++ b/core/Migrations/Version28000Date20231103104802.php @@ -0,0 +1,60 @@ + + * + * @author Marcel Klehr + * + * @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 . + * + */ + +namespace OC\Core\Migrations; + +use Closure; +use OCP\DB\ISchemaWrapper; +use OCP\DB\Types; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + +/** + * Introduce completion_expected_at column in textprocessing_tasks table + */ +class Version28000Date20231103104802 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('textprocessing_tasks')) { + $table = $schema->getTable('textprocessing_tasks'); + + $table->addColumn('completion_expected_at', Types::DATETIME, [ + 'notnull' => false, + ]); + + return $schema; + } + + return null; + } +} diff --git a/core/ResponseDefinitions.php b/core/ResponseDefinitions.php index ca3f117051c4c..6e51b7bd9b72e 100644 --- a/core/ResponseDefinitions.php +++ b/core/ResponseDefinitions.php @@ -143,6 +143,7 @@ * input: string, * output: ?string, * identifier: string, + * completionExpectedAt: ?int * } * * @psalm-type CoreTextToImageTask = array{ diff --git a/lib/private/TextProcessing/Db/Task.php b/lib/private/TextProcessing/Db/Task.php index 9c6f16d11ae3b..5f362d429f347 100644 --- a/lib/private/TextProcessing/Db/Task.php +++ b/lib/private/TextProcessing/Db/Task.php @@ -45,6 +45,8 @@ * @method string getAppId() * @method setIdentifier(string $identifier) * @method string getIdentifier() + * @method setCompletionExpectedAt(null|\DateTime $completionExpectedAt) + * @method null|\DateTime getCompletionExpectedAt() */ class Task extends Entity { protected $lastUpdated; @@ -55,16 +57,17 @@ class Task extends Entity { protected $userId; protected $appId; protected $identifier; + protected $completionExpectedAt; /** * @var string[] */ - public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'identifier']; + public static array $columns = ['id', 'last_updated', 'type', 'input', 'output', 'status', 'user_id', 'app_id', 'identifier', 'completion_expected_at']; /** * @var string[] */ - public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'identifier']; + public static array $fields = ['id', 'lastUpdated', 'type', 'input', 'output', 'status', 'userId', 'appId', 'identifier', 'completionExpectedAt']; public function __construct() { @@ -78,6 +81,7 @@ public function __construct() { $this->addType('userId', 'string'); $this->addType('appId', 'string'); $this->addType('identifier', 'string'); + $this->addType('completionExpectedAt', 'datetime'); } public function toRow(): array { @@ -98,6 +102,7 @@ public static function fromPublicTask(OCPTask $task): Task { 'userId' => $task->getUserId(), 'appId' => $task->getAppId(), 'identifier' => $task->getIdentifier(), + 'completionExpectedAt' => $task->getCompletionExpectedAt(), ]); return $task; } @@ -107,6 +112,7 @@ public function toPublicTask(): OCPTask { $task->setId($this->getId()); $task->setStatus($this->getStatus()); $task->setOutput($this->getOutput()); + $task->setCompletionExpectedAt($this->getCompletionExpectedAt()); return $task; } } diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php index b9cb06c298e25..bee70b53a339b 100644 --- a/lib/private/TextProcessing/Manager.php +++ b/lib/private/TextProcessing/Manager.php @@ -28,6 +28,7 @@ use OC\AppFramework\Bootstrap\Coordinator; use OC\TextProcessing\Db\Task as DbTask; use OCP\IConfig; +use OCP\TextProcessing\IProvider2; use OCP\TextProcessing\Task; use OCP\TextProcessing\Task as OCPTask; use OC\TextProcessing\Db\TaskMapper; @@ -114,19 +115,7 @@ public function runTask(OCPTask $task): string { if (!$this->canHandleTask($task)) { throw new PreConditionNotMetException('No text processing provider is installed that can handle this task'); } - $providers = $this->getProviders(); - $json = $this->config->getAppValue('core', 'ai.textprocessing_provider_preferences', ''); - if ($json !== '') { - $preferences = json_decode($json, true); - if (isset($preferences[$task->getType()])) { - // If a preference for this task type is set, move the preferred provider to the start - $provider = current(array_filter($providers, fn ($provider) => $provider::class === $preferences[$task->getType()])); - if ($provider !== false) { - $providers = array_filter($providers, fn ($p) => $p !== $provider); - array_unshift($providers, $provider); - } - } - } + $providers = $this->getPreferredProviders($task); foreach ($providers as $provider) { if (!$task->canUseProvider($provider)) { @@ -134,6 +123,11 @@ public function runTask(OCPTask $task): string { } try { $task->setStatus(OCPTask::STATUS_RUNNING); + if ($provider instanceof IProvider2) { + $completionExpectedAt = new \DateTime('now'); + $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S')); + $task->setCompletionExpectedAt($completionExpectedAt); + } if ($task->getId() === null) { $taskEntity = $this->taskMapper->insert(DbTask::fromPublicTask($task)); $task->setId($taskEntity->getId()); @@ -158,18 +152,25 @@ public function runTask(OCPTask $task): string { } } + $task->setStatus(OCPTask::STATUS_FAILED); + $this->taskMapper->update(DbTask::fromPublicTask($task)); throw new RuntimeException('Could not run task'); } /** * @inheritDoc - * @throws Exception */ public function scheduleTask(OCPTask $task): void { if (!$this->canHandleTask($task)) { throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task'); } $task->setStatus(OCPTask::STATUS_SCHEDULED); + [$provider, ] = $this->getPreferredProviders($task); + if ($provider instanceof IProvider2) { + $completionExpectedAt = new \DateTime('now'); + $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S')); + $task->setCompletionExpectedAt($completionExpectedAt); + } $taskEntity = DbTask::fromPublicTask($task); $this->taskMapper->insert($taskEntity); $task->setId($taskEntity->getId()); @@ -178,6 +179,25 @@ public function scheduleTask(OCPTask $task): void { ]); } + /** + * @inheritDoc + */ + public function runOrScheduleTask(OCPTask $task) : bool { + if (!$this->canHandleTask($task)) { + throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task'); + } + [$provider,] = $this->getPreferredProviders($task); + $maxExecutionTime = (int) ini_get('max_execution_time'); + // Offload the task to a background job if the expected runtime of the likely provider is longer than 80% of our max execution time + // or if the provider doesn't provide a getExpectedRuntime() method + if (!$provider instanceof IProvider2 || $provider->getExpectedRuntime() > $maxExecutionTime * 0.8) { + $this->scheduleTask($task); + return false; + } + $this->runTask($task); + return true; + } + /** * @inheritDoc */ @@ -253,4 +273,25 @@ public function getUserTasksByApp(string $userId, string $appId, ?string $identi throw new RuntimeException('Failure while trying to find tasks by appId and identifier: ' . $e->getMessage(), 0, $e); } } + + /** + * @param OCPTask $task + * @return IProvider[] + */ + public function getPreferredProviders(OCPTask $task): array { + $providers = $this->getProviders(); + $json = $this->config->getAppValue('core', 'ai.textprocessing_provider_preferences', ''); + if ($json !== '') { + $preferences = json_decode($json, true); + if (isset($preferences[$task->getType()])) { + // If a preference for this task type is set, move the preferred provider to the start + $provider = current(array_filter($providers, fn ($provider) => $provider::class === $preferences[$task->getType()])); + if ($provider !== false) { + $providers = array_filter($providers, fn ($p) => $p !== $provider); + array_unshift($providers, $provider); + } + } + } + return $providers; + } } diff --git a/lib/public/TextProcessing/IManager.php b/lib/public/TextProcessing/IManager.php index dec0baba4bb74..aae686e318cfd 100644 --- a/lib/public/TextProcessing/IManager.php +++ b/lib/public/TextProcessing/IManager.php @@ -27,6 +27,7 @@ namespace OCP\TextProcessing; use OCP\Common\Exception\NotFoundException; +use OCP\DB\Exception; use OCP\PreConditionNotMetException; use RuntimeException; @@ -68,10 +69,25 @@ public function runTask(Task $task): string; * * @param Task $task The task to schedule * @throws PreConditionNotMetException If no or not the requested provider was registered but this method was still called + * @throws Exception storing the task in the database failed * @since 27.1.0 */ public function scheduleTask(Task $task) : void; + /** + * If the designated provider for the passed task provides an expected average runtime, we check if the runtime fits into the + * max execution time of this php process and run it synchronously if it does, if it doesn't fit (or the provider doesn't provide that information) + * execution is deferred to a background job + * + * @param Task $task The task to schedule + * @returns bool A boolean indicating whether the task was run synchronously (`true`) or offloaded to a background job (`false`) + * @throws PreConditionNotMetException If no or not the requested provider was registered but this method was still called + * @throws RuntimeException If running the task failed + * @throws Exception storing the task in the database failed + * @since 28.0.0 + */ + public function runOrScheduleTask(Task $task): bool; + /** * Delete a task that has been scheduled before * diff --git a/lib/public/TextProcessing/IProvider2.php b/lib/public/TextProcessing/IProvider2.php new file mode 100644 index 0000000000000..74e6a47fed5f0 --- /dev/null +++ b/lib/public/TextProcessing/IProvider2.php @@ -0,0 +1,48 @@ + + * + * @author Marcel Klehr + * + * @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 . + */ + + +namespace OCP\TextProcessing; + +/** + * This interface supersedes IProvider. It allows the system to learn + * the provider's expected runtime and lets the provider know which user is running a task + * @since 28.0.0 + * @template T of ITaskType + * @template-extends IProvider + */ +interface IProvider2 extends IProvider { + /** + * @param ?string $userId the current user's id + * @since 28.0.0 + */ + public function setUserId(?string $userId): string; + + /** + * @return int The expected average runtime of a task in seconds + * @since 28.0.0 + */ + public function getExpectedRuntime(): int; +} diff --git a/lib/public/TextProcessing/Task.php b/lib/public/TextProcessing/Task.php index 446e414cb045c..16850348358d4 100644 --- a/lib/public/TextProcessing/Task.php +++ b/lib/public/TextProcessing/Task.php @@ -35,6 +35,7 @@ final class Task implements \JsonSerializable { protected ?int $id = null; protected ?string $output = null; + private ?\DateTime $completionExpectedAt = null; /** * @since 27.1.0 @@ -92,12 +93,15 @@ final public function __construct( /** * @psalm-param P $provider - * @param IProvider $provider + * @param IProvider|IProvider2 $provider * @return string * @since 27.1.0 */ - public function visitProvider(IProvider $provider): string { + public function visitProvider(IProvider|IProvider2 $provider): string { if ($this->canUseProvider($provider)) { + if ($provider instanceof IProvider2) { + $provider->setUserId($this->getUserId()); + } return $provider->process($this->getInput()); } else { throw new \RuntimeException('Task of type ' . $this->getType() . ' cannot visit provider with task type ' . $provider->getTaskType()); @@ -203,7 +207,7 @@ final public function getUserId(): ?string { } /** - * @psalm-return array{id: ?int, type: S, status: 0|1|2|3|4, userId: ?string, appId: string, input: string, output: ?string, identifier: string} + * @psalm-return array{id: ?int, type: S, status: 0|1|2|3|4, userId: ?string, appId: string, input: string, output: ?string, identifier: string, completionExpectedAt: ?int} * @since 27.1.0 */ public function jsonSerialize(): array { @@ -216,6 +220,15 @@ public function jsonSerialize(): array { 'input' => $this->getInput(), 'output' => $this->getOutput(), 'identifier' => $this->getIdentifier(), + 'completionExpectedAt' => $this->getCompletionExpectedAt()?->getTimestamp(), ]; } + +final public function setCompletionExpectedAt(\DateTime $completionExpectedAt): void { + $this->completionExpectedAt = $completionExpectedAt; +} + +final public function getCompletionExpectedAt(): ?\DateTime { + return $this->completionExpectedAt; +} } From d11b9cbd7993042fcf9ba49d5c8ef14bf928d901 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 6 Nov 2023 12:50:16 +0100 Subject: [PATCH 02/14] fix(TextProcessing/Manager): Throw TaskFailureException upon failure Signed-off-by: Marcel Klehr --- core/Controller/TextProcessingApiController.php | 3 ++- lib/private/TextProcessing/Manager.php | 10 +++------- .../TextProcessing/Exception/TaskFailureException.php | 7 +++++++ lib/public/TextProcessing/IManager.php | 5 +++-- 4 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 lib/public/TextProcessing/Exception/TaskFailureException.php diff --git a/core/Controller/TextProcessingApiController.php b/core/Controller/TextProcessingApiController.php index 6b975a7ed61ca..a6b85fd46ae84 100644 --- a/core/Controller/TextProcessingApiController.php +++ b/core/Controller/TextProcessingApiController.php @@ -38,6 +38,7 @@ use OCP\DB\Exception; use OCP\IL10N; use OCP\IRequest; +use OCP\TextProcessing\Exception\TaskFailureException; use OCP\TextProcessing\ITaskType; use OCP\TextProcessing\Task; use OCP\TextProcessing\IManager; @@ -121,7 +122,7 @@ public function schedule(string $input, string $type, string $appId, string $ide try { try { $this->textProcessingManager->runOrScheduleTask($task); - } catch(\RuntimeException) { + } catch(TaskFailureException) { // noop, because the task object has the failure status set already, we just return the task json } diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php index bee70b53a339b..439ffde452186 100644 --- a/lib/private/TextProcessing/Manager.php +++ b/lib/private/TextProcessing/Manager.php @@ -28,6 +28,7 @@ use OC\AppFramework\Bootstrap\Coordinator; use OC\TextProcessing\Db\Task as DbTask; use OCP\IConfig; +use OCP\TextProcessing\Exception\TaskFailureException; use OCP\TextProcessing\IProvider2; use OCP\TextProcessing\Task; use OCP\TextProcessing\Task as OCPTask; @@ -139,22 +140,17 @@ public function runTask(OCPTask $task): string { $task->setStatus(OCPTask::STATUS_SUCCESSFUL); $this->taskMapper->update(DbTask::fromPublicTask($task)); return $output; - } catch (\RuntimeException $e) { - $this->logger->info('LanguageModel call using provider ' . $provider->getName() . ' failed', ['exception' => $e]); - $task->setStatus(OCPTask::STATUS_FAILED); - $this->taskMapper->update(DbTask::fromPublicTask($task)); - throw $e; } catch (\Throwable $e) { $this->logger->info('LanguageModel call using provider ' . $provider->getName() . ' failed', ['exception' => $e]); $task->setStatus(OCPTask::STATUS_FAILED); $this->taskMapper->update(DbTask::fromPublicTask($task)); - throw new RuntimeException('LanguageModel call using provider ' . $provider->getName() . ' failed: ' . $e->getMessage(), 0, $e); + throw new TaskFailureException('LanguageModel call using provider ' . $provider->getName() . ' failed: ' . $e->getMessage(), 0, $e); } } $task->setStatus(OCPTask::STATUS_FAILED); $this->taskMapper->update(DbTask::fromPublicTask($task)); - throw new RuntimeException('Could not run task'); + throw new TaskFailureException('Could not run task'); } /** diff --git a/lib/public/TextProcessing/Exception/TaskFailureException.php b/lib/public/TextProcessing/Exception/TaskFailureException.php new file mode 100644 index 0000000000000..5f7b308757b44 --- /dev/null +++ b/lib/public/TextProcessing/Exception/TaskFailureException.php @@ -0,0 +1,7 @@ + Date: Mon, 6 Nov 2023 12:54:58 +0100 Subject: [PATCH 03/14] fix(TextProcessing/Manager): add canuseProvider check in getPreferredProviders Signed-off-by: Marcel Klehr --- lib/private/TextProcessing/Manager.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php index 439ffde452186..ee4fc70a06a09 100644 --- a/lib/private/TextProcessing/Manager.php +++ b/lib/private/TextProcessing/Manager.php @@ -119,9 +119,6 @@ public function runTask(OCPTask $task): string { $providers = $this->getPreferredProviders($task); foreach ($providers as $provider) { - if (!$task->canUseProvider($provider)) { - continue; - } try { $task->setStatus(OCPTask::STATUS_RUNNING); if ($provider instanceof IProvider2) { @@ -288,6 +285,7 @@ public function getPreferredProviders(OCPTask $task): array { } } } + $providers = array_filter($providers, fn (IProvider $provider) => $task->canUseProvider($provider)); return $providers; } } From 1476a7216a6b753849f1aca944357d184fed8b73 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 6 Nov 2023 12:57:57 +0100 Subject: [PATCH 04/14] fix(TextProcessing/ migration): add check whether new column exists already Signed-off-by: Marcel Klehr --- core/Migrations/Version28000Date20231103104802.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/Migrations/Version28000Date20231103104802.php b/core/Migrations/Version28000Date20231103104802.php index 69dddbccee93f..aa54f1c584864 100644 --- a/core/Migrations/Version28000Date20231103104802.php +++ b/core/Migrations/Version28000Date20231103104802.php @@ -48,11 +48,12 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt if ($schema->hasTable('textprocessing_tasks')) { $table = $schema->getTable('textprocessing_tasks'); - $table->addColumn('completion_expected_at', Types::DATETIME, [ - 'notnull' => false, - ]); - - return $schema; + if (!$table->hasColumn('completion_expected_at')) { + $table->addColumn('completion_expected_at', Types::DATETIME, [ + 'notnull' => false, + ]); + return $schema; + } } return null; From b01e0c31fbf833035bc9e10d7344fcd67071906b Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 6 Nov 2023 14:52:59 +0100 Subject: [PATCH 05/14] enh: update openapi.json Signed-off-by: Marcel Klehr --- core/openapi.json | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/core/openapi.json b/core/openapi.json index 7cb48b58f0a4c..29d5dedc48c27 100644 --- a/core/openapi.json +++ b/core/openapi.json @@ -416,7 +416,8 @@ "appId", "input", "output", - "identifier" + "identifier", + "completionExpectedAt" ], "properties": { "id": { @@ -447,6 +448,11 @@ }, "identifier": { "type": "string" + }, + "completionExpectedAt": { + "type": "integer", + "format": "int64", + "nullable": true } } }, @@ -4661,6 +4667,44 @@ } } }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + } + } + } + }, "400": { "description": "Scheduling task is not possible", "content": { From fecf6425cff5d3d67fd874780189d053e70a0074 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Mon, 6 Nov 2023 14:53:20 +0100 Subject: [PATCH 06/14] fix: psalm issues and coding style Signed-off-by: Marcel Klehr --- .../Exception/TaskFailureException.php | 5 ++++- lib/public/TextProcessing/Task.php | 21 +++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/public/TextProcessing/Exception/TaskFailureException.php b/lib/public/TextProcessing/Exception/TaskFailureException.php index 5f7b308757b44..300864711e773 100644 --- a/lib/public/TextProcessing/Exception/TaskFailureException.php +++ b/lib/public/TextProcessing/Exception/TaskFailureException.php @@ -2,6 +2,9 @@ namespace OCP\TextProcessing\Exception; +/** + * Exception thrown when a task failed + * @since 28.0.0 + */ class TaskFailureException extends \RuntimeException { - } diff --git a/lib/public/TextProcessing/Task.php b/lib/public/TextProcessing/Task.php index 16850348358d4..f6b3a1a6a427b 100644 --- a/lib/public/TextProcessing/Task.php +++ b/lib/public/TextProcessing/Task.php @@ -224,11 +224,20 @@ public function jsonSerialize(): array { ]; } -final public function setCompletionExpectedAt(\DateTime $completionExpectedAt): void { - $this->completionExpectedAt = $completionExpectedAt; -} + /** + * @param null|\DateTime $completionExpectedAt + * @return void + * @since 28.0.0 + */ + final public function setCompletionExpectedAt(?\DateTime $completionExpectedAt): void { + $this->completionExpectedAt = $completionExpectedAt; + } -final public function getCompletionExpectedAt(): ?\DateTime { - return $this->completionExpectedAt; -} + /** + * @return \DateTime|null + * @since 28.0.0 + */ + final public function getCompletionExpectedAt(): ?\DateTime { + return $this->completionExpectedAt; + } } From 17e7b6c7110e11c977852da91d62cab12cde0b49 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 7 Nov 2023 12:02:03 +0100 Subject: [PATCH 07/14] fix: split IProvider2 into two more verbose interfaces Signed-off-by: Marcel Klehr --- lib/composer/composer/autoload_classmap.php | 4 ++ lib/composer/composer/autoload_static.php | 4 ++ lib/private/TextProcessing/Manager.php | 8 ++-- ...2.php => IProviderWithExpectedRuntime.php} | 10 +---- .../TextProcessing/IProviderWithUserId.php | 41 +++++++++++++++++++ lib/public/TextProcessing/Task.php | 6 +-- 6 files changed, 58 insertions(+), 15 deletions(-) rename lib/public/TextProcessing/{IProvider2.php => IProviderWithExpectedRuntime.php} (77%) create mode 100644 lib/public/TextProcessing/IProviderWithUserId.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index b2d0b2255749b..3367077d83359 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -666,10 +666,13 @@ 'OCP\\TextProcessing\\Events\\AbstractTextProcessingEvent' => $baseDir . '/lib/public/TextProcessing/Events/AbstractTextProcessingEvent.php', 'OCP\\TextProcessing\\Events\\TaskFailedEvent' => $baseDir . '/lib/public/TextProcessing/Events/TaskFailedEvent.php', 'OCP\\TextProcessing\\Events\\TaskSuccessfulEvent' => $baseDir . '/lib/public/TextProcessing/Events/TaskSuccessfulEvent.php', + 'OCP\\TextProcessing\\Exception\\TaskFailureException' => $baseDir . '/lib/public/TextProcessing/Exception/TaskFailureException.php', 'OCP\\TextProcessing\\FreePromptTaskType' => $baseDir . '/lib/public/TextProcessing/FreePromptTaskType.php', 'OCP\\TextProcessing\\HeadlineTaskType' => $baseDir . '/lib/public/TextProcessing/HeadlineTaskType.php', 'OCP\\TextProcessing\\IManager' => $baseDir . '/lib/public/TextProcessing/IManager.php', 'OCP\\TextProcessing\\IProvider' => $baseDir . '/lib/public/TextProcessing/IProvider.php', + 'OCP\\TextProcessing\\IProviderWithExpectedRuntime' => $baseDir . '/lib/public/TextProcessing/IProviderWithExpectedRuntime.php', + 'OCP\\TextProcessing\\IProviderWithUserId' => $baseDir . '/lib/public/TextProcessing/IProviderWithUserId.php', 'OCP\\TextProcessing\\ITaskType' => $baseDir . '/lib/public/TextProcessing/ITaskType.php', 'OCP\\TextProcessing\\SummaryTaskType' => $baseDir . '/lib/public/TextProcessing/SummaryTaskType.php', 'OCP\\TextProcessing\\Task' => $baseDir . '/lib/public/TextProcessing/Task.php', @@ -1196,6 +1199,7 @@ 'OC\\Core\\Migrations\\Version28000Date20230728104802' => $baseDir . '/core/Migrations/Version28000Date20230728104802.php', 'OC\\Core\\Migrations\\Version28000Date20230803221055' => $baseDir . '/core/Migrations/Version28000Date20230803221055.php', 'OC\\Core\\Migrations\\Version28000Date20230906104802' => $baseDir . '/core/Migrations/Version28000Date20230906104802.php', + 'OC\\Core\\Migrations\\Version28000Date20231103104802' => $baseDir . '/core/Migrations/Version28000Date20231103104802.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', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 7e73255b29b2e..0f7e2de64ef79 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -699,10 +699,13 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\TextProcessing\\Events\\AbstractTextProcessingEvent' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Events/AbstractTextProcessingEvent.php', 'OCP\\TextProcessing\\Events\\TaskFailedEvent' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Events/TaskFailedEvent.php', 'OCP\\TextProcessing\\Events\\TaskSuccessfulEvent' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Events/TaskSuccessfulEvent.php', + 'OCP\\TextProcessing\\Exception\\TaskFailureException' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Exception/TaskFailureException.php', 'OCP\\TextProcessing\\FreePromptTaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/FreePromptTaskType.php', 'OCP\\TextProcessing\\HeadlineTaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/HeadlineTaskType.php', 'OCP\\TextProcessing\\IManager' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IManager.php', 'OCP\\TextProcessing\\IProvider' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IProvider.php', + 'OCP\\TextProcessing\\IProviderWithExpectedRuntime' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IProviderWithExpectedRuntime.php', + 'OCP\\TextProcessing\\IProviderWithUserId' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/IProviderWithUserId.php', 'OCP\\TextProcessing\\ITaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/ITaskType.php', 'OCP\\TextProcessing\\SummaryTaskType' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/SummaryTaskType.php', 'OCP\\TextProcessing\\Task' => __DIR__ . '/../../..' . '/lib/public/TextProcessing/Task.php', @@ -1229,6 +1232,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Migrations\\Version28000Date20230728104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230728104802.php', 'OC\\Core\\Migrations\\Version28000Date20230803221055' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230803221055.php', 'OC\\Core\\Migrations\\Version28000Date20230906104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230906104802.php', + 'OC\\Core\\Migrations\\Version28000Date20231103104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20231103104802.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', diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php index ee4fc70a06a09..d1225b0f12fca 100644 --- a/lib/private/TextProcessing/Manager.php +++ b/lib/private/TextProcessing/Manager.php @@ -29,7 +29,7 @@ use OC\TextProcessing\Db\Task as DbTask; use OCP\IConfig; use OCP\TextProcessing\Exception\TaskFailureException; -use OCP\TextProcessing\IProvider2; +use OCP\TextProcessing\IProviderWithExpectedRuntime; use OCP\TextProcessing\Task; use OCP\TextProcessing\Task as OCPTask; use OC\TextProcessing\Db\TaskMapper; @@ -121,7 +121,7 @@ public function runTask(OCPTask $task): string { foreach ($providers as $provider) { try { $task->setStatus(OCPTask::STATUS_RUNNING); - if ($provider instanceof IProvider2) { + if ($provider instanceof IProviderWithExpectedRuntime) { $completionExpectedAt = new \DateTime('now'); $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S')); $task->setCompletionExpectedAt($completionExpectedAt); @@ -159,7 +159,7 @@ public function scheduleTask(OCPTask $task): void { } $task->setStatus(OCPTask::STATUS_SCHEDULED); [$provider, ] = $this->getPreferredProviders($task); - if ($provider instanceof IProvider2) { + if ($provider instanceof IProviderWithExpectedRuntime) { $completionExpectedAt = new \DateTime('now'); $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S')); $task->setCompletionExpectedAt($completionExpectedAt); @@ -183,7 +183,7 @@ public function runOrScheduleTask(OCPTask $task) : bool { $maxExecutionTime = (int) ini_get('max_execution_time'); // Offload the task to a background job if the expected runtime of the likely provider is longer than 80% of our max execution time // or if the provider doesn't provide a getExpectedRuntime() method - if (!$provider instanceof IProvider2 || $provider->getExpectedRuntime() > $maxExecutionTime * 0.8) { + if (!$provider instanceof IProviderWithExpectedRuntime || $provider->getExpectedRuntime() > $maxExecutionTime * 0.8) { $this->scheduleTask($task); return false; } diff --git a/lib/public/TextProcessing/IProvider2.php b/lib/public/TextProcessing/IProviderWithExpectedRuntime.php similarity index 77% rename from lib/public/TextProcessing/IProvider2.php rename to lib/public/TextProcessing/IProviderWithExpectedRuntime.php index 74e6a47fed5f0..6eb3dcc8cde4c 100644 --- a/lib/public/TextProcessing/IProvider2.php +++ b/lib/public/TextProcessing/IProviderWithExpectedRuntime.php @@ -27,18 +27,12 @@ namespace OCP\TextProcessing; /** - * This interface supersedes IProvider. It allows the system to learn - * the provider's expected runtime and lets the provider know which user is running a task + * This interface allows the system to learn the provider's expected runtime * @since 28.0.0 * @template T of ITaskType * @template-extends IProvider */ -interface IProvider2 extends IProvider { - /** - * @param ?string $userId the current user's id - * @since 28.0.0 - */ - public function setUserId(?string $userId): string; +interface IProviderWithExpectedRuntime extends IProvider { /** * @return int The expected average runtime of a task in seconds diff --git a/lib/public/TextProcessing/IProviderWithUserId.php b/lib/public/TextProcessing/IProviderWithUserId.php new file mode 100644 index 0000000000000..e12317f52f750 --- /dev/null +++ b/lib/public/TextProcessing/IProviderWithUserId.php @@ -0,0 +1,41 @@ + + * + * @author Marcel Klehr + * + * @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 . + */ + + +namespace OCP\TextProcessing; + +/** + * This interface allows providers to access the user that initiated the task being run. + * @since 28.0.0 + * @template T of ITaskType + * @template-extends IProvider + */ +interface IProviderWithUserId extends IProvider { + /** + * @param ?string $userId the current user's id + * @since 28.0.0 + */ + public function setUserId(?string $userId): string; +} diff --git a/lib/public/TextProcessing/Task.php b/lib/public/TextProcessing/Task.php index f6b3a1a6a427b..25b7132ee3112 100644 --- a/lib/public/TextProcessing/Task.php +++ b/lib/public/TextProcessing/Task.php @@ -93,13 +93,13 @@ final public function __construct( /** * @psalm-param P $provider - * @param IProvider|IProvider2 $provider + * @param IProvider $provider * @return string * @since 27.1.0 */ - public function visitProvider(IProvider|IProvider2 $provider): string { + public function visitProvider(IProvider $provider): string { if ($this->canUseProvider($provider)) { - if ($provider instanceof IProvider2) { + if ($provider instanceof IProviderWithUserId) { $provider->setUserId($this->getUserId()); } return $provider->process($this->getInput()); From b8862bd14ed778b6de107b1e2ba495ef5e5fc896 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 7 Nov 2023 16:29:15 +0100 Subject: [PATCH 08/14] fix coding style Signed-off-by: Marcel Klehr --- lib/public/TextProcessing/IProviderWithExpectedRuntime.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/public/TextProcessing/IProviderWithExpectedRuntime.php b/lib/public/TextProcessing/IProviderWithExpectedRuntime.php index 6eb3dcc8cde4c..17767fc02d46f 100644 --- a/lib/public/TextProcessing/IProviderWithExpectedRuntime.php +++ b/lib/public/TextProcessing/IProviderWithExpectedRuntime.php @@ -33,7 +33,6 @@ * @template-extends IProvider */ interface IProviderWithExpectedRuntime extends IProvider { - /** * @return int The expected average runtime of a task in seconds * @since 28.0.0 From 12c558f20ad27237f4650aca53056ae7e263e459 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 8 Nov 2023 17:00:48 +0100 Subject: [PATCH 09/14] Update lib/private/TextProcessing/Manager.php Co-authored-by: Julien Veyssier Signed-off-by: Marcel Klehr --- lib/private/TextProcessing/Manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php index d1225b0f12fca..d6633568553fb 100644 --- a/lib/private/TextProcessing/Manager.php +++ b/lib/private/TextProcessing/Manager.php @@ -175,7 +175,7 @@ public function scheduleTask(OCPTask $task): void { /** * @inheritDoc */ - public function runOrScheduleTask(OCPTask $task) : bool { + public function runOrScheduleTask(OCPTask $task): bool { if (!$this->canHandleTask($task)) { throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task'); } From 6fbf430fe30a90d900397a28a3357d0e1fc7ffb5 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 8 Nov 2023 17:01:02 +0100 Subject: [PATCH 10/14] Update lib/private/TextProcessing/Manager.php Co-authored-by: Julien Veyssier Signed-off-by: Marcel Klehr --- lib/private/TextProcessing/Manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php index d6633568553fb..c165f22991936 100644 --- a/lib/private/TextProcessing/Manager.php +++ b/lib/private/TextProcessing/Manager.php @@ -158,7 +158,7 @@ public function scheduleTask(OCPTask $task): void { throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task'); } $task->setStatus(OCPTask::STATUS_SCHEDULED); - [$provider, ] = $this->getPreferredProviders($task); + [$provider,] = $this->getPreferredProviders($task); if ($provider instanceof IProviderWithExpectedRuntime) { $completionExpectedAt = new \DateTime('now'); $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S')); From a29ed70c6cac8284f1de029b66b2798b6a16d519 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Wed, 8 Nov 2023 17:01:10 +0100 Subject: [PATCH 11/14] Update lib/public/TextProcessing/IProviderWithUserId.php Co-authored-by: Julien Veyssier Signed-off-by: Marcel Klehr --- lib/public/TextProcessing/IProviderWithUserId.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/public/TextProcessing/IProviderWithUserId.php b/lib/public/TextProcessing/IProviderWithUserId.php index e12317f52f750..0a01a4c56c42d 100644 --- a/lib/public/TextProcessing/IProviderWithUserId.php +++ b/lib/public/TextProcessing/IProviderWithUserId.php @@ -37,5 +37,5 @@ interface IProviderWithUserId extends IProvider { * @param ?string $userId the current user's id * @since 28.0.0 */ - public function setUserId(?string $userId): string; + public function setUserId(?string $userId): void; } From 5704281276274084fa9b9031633d0f460fbcb377 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 9 Nov 2023 15:34:22 +0100 Subject: [PATCH 12/14] fix: Update autoloaders Signed-off-by: Marcel Klehr --- lib/composer/composer/autoload_classmap.php | 2 +- lib/composer/composer/autoload_static.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 484e980f101c0..05a3483d5a44e 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1213,8 +1213,8 @@ 'OC\\Core\\Migrations\\Version28000Date20230728104802' => $baseDir . '/core/Migrations/Version28000Date20230728104802.php', 'OC\\Core\\Migrations\\Version28000Date20230803221055' => $baseDir . '/core/Migrations/Version28000Date20230803221055.php', 'OC\\Core\\Migrations\\Version28000Date20230906104802' => $baseDir . '/core/Migrations/Version28000Date20230906104802.php', - 'OC\\Core\\Migrations\\Version28000Date20231103104802' => $baseDir . '/core/Migrations/Version28000Date20231103104802.php', 'OC\\Core\\Migrations\\Version28000Date20231004103301' => $baseDir . '/core/Migrations/Version28000Date20231004103301.php', + 'OC\\Core\\Migrations\\Version28000Date20231103104802' => $baseDir . '/core/Migrations/Version28000Date20231103104802.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', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 8f71a50f4cbff..6a3e9a11b9f96 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1246,8 +1246,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Migrations\\Version28000Date20230728104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230728104802.php', 'OC\\Core\\Migrations\\Version28000Date20230803221055' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230803221055.php', 'OC\\Core\\Migrations\\Version28000Date20230906104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20230906104802.php', - 'OC\\Core\\Migrations\\Version28000Date20231103104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20231103104802.php', 'OC\\Core\\Migrations\\Version28000Date20231004103301' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20231004103301.php', + 'OC\\Core\\Migrations\\Version28000Date20231103104802' => __DIR__ . '/../../..' . '/core/Migrations/Version28000Date20231103104802.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', From 017f1360597deabd989b60727c514fb8880e3e24 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Thu, 9 Nov 2023 15:38:59 +0100 Subject: [PATCH 13/14] fix: Don't try to access undefined array key Signed-off-by: Marcel Klehr --- lib/private/TextProcessing/Manager.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php index c165f22991936..41a4a5dedadd7 100644 --- a/lib/private/TextProcessing/Manager.php +++ b/lib/private/TextProcessing/Manager.php @@ -158,7 +158,11 @@ public function scheduleTask(OCPTask $task): void { throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task'); } $task->setStatus(OCPTask::STATUS_SCHEDULED); - [$provider,] = $this->getPreferredProviders($task); + $providers = $this->getPreferredProviders($task); + if (count($providers) === 0) { + throw new PreConditionNotMetException('No LanguageModel provider is installed that can handle this task'); + } + [$provider,] = $providers; if ($provider instanceof IProviderWithExpectedRuntime) { $completionExpectedAt = new \DateTime('now'); $completionExpectedAt->add(new \DateInterval('PT'.$provider->getExpectedRuntime().'S')); From 2031fe17b4f6b48d3b72fee520285dde37488afb Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 10 Nov 2023 11:13:54 +0100 Subject: [PATCH 14/14] fix: Make sure array starts at 0 after array filter Signed-off-by: Marcel Klehr --- lib/private/TextProcessing/Manager.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/private/TextProcessing/Manager.php b/lib/private/TextProcessing/Manager.php index 41a4a5dedadd7..c98ff89314371 100644 --- a/lib/private/TextProcessing/Manager.php +++ b/lib/private/TextProcessing/Manager.php @@ -282,14 +282,14 @@ public function getPreferredProviders(OCPTask $task): array { $preferences = json_decode($json, true); if (isset($preferences[$task->getType()])) { // If a preference for this task type is set, move the preferred provider to the start - $provider = current(array_filter($providers, fn ($provider) => $provider::class === $preferences[$task->getType()])); + $provider = current(array_values(array_filter($providers, fn ($provider) => $provider::class === $preferences[$task->getType()]))); if ($provider !== false) { $providers = array_filter($providers, fn ($p) => $p !== $provider); array_unshift($providers, $provider); } } } - $providers = array_filter($providers, fn (IProvider $provider) => $task->canUseProvider($provider)); + $providers = array_values(array_filter($providers, fn (IProvider $provider) => $task->canUseProvider($provider))); return $providers; } }