%PDF- %PDF-
| Direktori : /www/varak.net/nextcloud.varak.net/apps/app_api/lib/Service/ProvidersAI/ |
| Current File : //www/varak.net/nextcloud.varak.net/apps/app_api/lib/Service/ProvidersAI/TaskProcessingService.php |
<?php
declare(strict_types=1);
namespace OCA\AppAPI\Service\ProvidersAI;
use JsonException;
use OCA\AppAPI\AppInfo\Application;
use OCA\AppAPI\Db\TaskProcessing\TaskProcessingProvider;
use OCA\AppAPI\Db\TaskProcessing\TaskProcessingProviderMapper;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\DB\Exception;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IServerContainer;
use OCP\TaskProcessing\EShapeType;
use OCP\TaskProcessing\IProvider;
use OCP\TaskProcessing\ITaskType;
use OCP\TaskProcessing\ShapeDescriptor;
use OCP\TaskProcessing\ShapeEnumValue;
use Psr\Log\LoggerInterface;
class TaskProcessingService {
private ?ICache $cache = null;
public function __construct(
ICacheFactory $cacheFactory,
private readonly TaskProcessingProviderMapper $mapper,
private readonly LoggerInterface $logger,
) {
if ($cacheFactory->isAvailable()) {
$this->cache = $cacheFactory->createDistributed(Application::APP_ID . '/ex_task_processing_providers');
}
}
/**
* Get list of registered TaskProcessing providers (only for enabled ExApps)
*
* @return TaskProcessingProvider[]
*/
public function getRegisteredTaskProcessingProviders(): array {
try {
$cacheKey = '/ex_task_processing_providers';
$records = $this->cache?->get($cacheKey);
if ($records === null) {
$records = $this->mapper->findAllEnabled();
$this->cache?->set($cacheKey, $records);
}
return array_map(static function ($record) {
return new TaskProcessingProvider($record);
}, $records);
} catch (Exception) {
return [];
}
}
public function getExAppTaskProcessingProvider(string $appId, string $name): ?TaskProcessingProvider {
foreach ($this->getRegisteredTaskProcessingProviders() as $provider) {
if (($provider->getAppId() === $appId) && ($provider->getName() === $name)) {
return $provider;
}
}
try {
return $this->mapper->findByAppIdName($appId, $name);
} catch (DoesNotExistException|MultipleObjectsReturnedException|Exception) {
return null;
}
}
private function everyElementHasKeys(array|null $array, array $keys): bool {
if (!is_array($array)) {
return false;
}
foreach ($array as $propertyName => $properties) {
if (!is_string($propertyName) || !is_array($properties)) {
return false;
}
foreach ($properties as $property) {
if (!is_array($property)) {
return false;
}
foreach ($keys as $key) {
if (!array_key_exists($key, $property)) {
return false;
}
}
}
}
return true;
}
private function validateTaskProcessingProvider(array $provider): void {
if (!isset($provider['id']) || !is_string($provider['id'])) {
throw new Exception('"id" key must be a string');
}
if (!isset($provider['name']) || !is_string($provider['name'])) {
throw new Exception('"name" key must be a string');
}
if (!isset($provider['task_type']) || !is_string($provider['task_type'])) {
throw new Exception('"task_type" key must be a string');
}
if (!isset($provider['expected_runtime']) || !is_int($provider['expected_runtime'])) {
throw new Exception('"expected_runtime" key must be an integer');
}
if (!$this->everyElementHasKeys($provider['optional_input_shape'], ['name', 'description', 'shape_type'])) {
throw new Exception('"optional_input_shape" should be an array and must have "name", "description" and "shape_type" keys');
}
if (!$this->everyElementHasKeys($provider['optional_output_shape'], ['name', 'description', 'shape_type'])) {
throw new Exception('"optional_output_shape" should be an array and must have "name", "description" and "shape_type" keys');
}
if (!$this->everyElementHasKeys($provider['input_shape_enum_values'], ['name', 'value'])) {
throw new Exception('"input_shape_enum_values" should be an array and must have "name" and "value" keys');
}
if (!isset($provider['input_shape_defaults']) || !is_array($provider['input_shape_defaults'])) {
throw new Exception('"input_shape_defaults" key must be an array');
}
if (!$this->everyElementHasKeys($provider['optional_input_shape_enum_values'], ['name', 'value'])) {
throw new Exception('"optional_input_shape_enum_values" should be an array and must have "name" and "value" keys');
}
if (!isset($provider['optional_input_shape_defaults']) || !is_array($provider['optional_input_shape_defaults'])) {
throw new Exception('"optional_input_shape_defaults" key must be an array');
}
if (!$this->everyElementHasKeys($provider['output_shape_enum_values'], ['name', 'value'])) {
throw new Exception('"output_shape_enum_values" should be an array and must have "name" and "value" keys');
}
if (!$this->everyElementHasKeys($provider['optional_output_shape_enum_values'], ['name', 'value'])) {
throw new Exception('"optional_output_shape_enum_values" should be an array and must have "name" and "value" keys');
}
}
public function registerTaskProcessingProvider(
string $appId,
array $provider,
?array $customTaskType,
): ?TaskProcessingProvider {
try {
if (is_array($customTaskType) && $provider['task_type'] !== $customTaskType['id']) {
throw new Exception('Task type and custom task type must be the same if custom task type is provided');
} elseif ($customTaskType === null && $provider['task_type'] === null) {
throw new Exception('Task type must be provided if custom task type is not provided');
}
$this->validateTaskProcessingProvider($provider);
} catch (Exception $e) {
$this->logger->error(
sprintf('Failed to register the ExApp "%s" TaskProcessingProvider "%s". Error: %s', $appId, $provider['name'] ?? '(no name found)', $e->getMessage()),
['exception' => $e],
);
return null;
}
$name = $provider['id'];
$displayName = $provider['name'];
$taskType = $provider['task_type'];
try {
$taskProcessingProvider = $this->mapper->findByAppidName($appId, $name);
} catch (DoesNotExistException|MultipleObjectsReturnedException|Exception) {
$taskProcessingProvider = null;
}
try {
$newTaskProcessingProvider = new TaskProcessingProvider([
'app_id' => $appId,
'name' => $name,
'display_name' => $displayName,
'task_type' => $taskType,
'provider' => json_encode($provider, JSON_THROW_ON_ERROR),
'custom_task_type' => json_encode($customTaskType, JSON_THROW_ON_ERROR),
]);
if ($taskProcessingProvider !== null) {
$newTaskProcessingProvider->setId($taskProcessingProvider->getId());
}
$taskProcessingProvider = $this->mapper->insertOrUpdate($newTaskProcessingProvider);
$this->resetCacheEnabled();
} catch (Exception $e) {
$this->logger->error(
sprintf('Failed to register ExApp "%s" TaskProcessingProvider "%s". Error: %s', $appId, $name, $e->getMessage()), ['exception' => $e]
);
return null;
}
return $taskProcessingProvider;
}
public function unregisterTaskProcessingProvider(string $appId, string $name): ?TaskProcessingProvider {
try {
$taskProcessingProvider = $this->getExAppTaskProcessingProvider($appId, $name);
if ($taskProcessingProvider !== null) {
$this->mapper->delete($taskProcessingProvider);
$this->resetCacheEnabled();
return $taskProcessingProvider;
}
} catch (Exception $e) {
$this->logger->error(sprintf('Failed to unregister ExApp %s TaskProcessingProvider %s. Error: %s', $appId, $name, $e->getMessage()), ['exception' => $e]);
}
return null;
}
/**
* Register dynamically ExApps TaskProcessing providers with ID using anonymous classes.
*
* @param IRegistrationContext $context
* @param IServerContainer $serverContainer
*
* @return void
*/
public function registerExAppTaskProcessingProviders(IRegistrationContext $context, IServerContainer $serverContainer): void {
$exAppsProviders = $this->getRegisteredTaskProcessingProviders();
foreach ($exAppsProviders as $exAppProvider) {
/** @var class-string<IProvider> $className */
$className = '\\OCA\\AppAPI\\' . $exAppProvider->getAppId() . '\\' . $exAppProvider->getName();
try {
$provider = $this->getAnonymousExAppProvider(json_decode($exAppProvider->getProvider(), true, flags: JSON_THROW_ON_ERROR));
} catch (JsonException $e) {
$this->logger->debug('Failed to register ExApp TaskProcessing provider', ['exAppId' => $exAppProvider->getAppId(), 'taskType' => $exAppProvider->getName(), 'exception' => $e]);
continue;
} catch (\Throwable) {
continue;
}
$context->registerService($className, function () use ($provider) {
return $provider;
});
$context->registerTaskProcessingProvider($className);
}
}
/**
* @psalm-suppress UndefinedClass, MissingDependency, InvalidReturnStatement, InvalidReturnType
*/
private function getAnonymousExAppProvider(
array $provider,
): IProvider {
return new class($provider) implements IProvider {
public function __construct(
private readonly array $provider,
) {
}
public function getId(): string {
return $this->provider['id'];
}
public function getName(): string {
return $this->provider['name'];
}
public function getTaskTypeId(): string {
return $this->provider['task_type'];
}
public function getExpectedRuntime(): int {
return $this->provider['expected_runtime'];
}
public function getOptionalInputShape(): array {
return array_map(function ($shape) {
return new ShapeDescriptor(
$shape['name'],
$shape['description'],
EShapeType::from($shape['shape_type']),
);
}, $this->provider['optional_input_shape']);
}
public function getOptionalOutputShape(): array {
return array_map(static function (array $shape) {
return new ShapeDescriptor(
$shape['name'],
$shape['description'],
EShapeType::from($shape['shape_type']),
);
}, $this->provider['optional_output_shape']);
}
public function getInputShapeEnumValues(): array {
return $this->arrayToTaskProcessingEnumValues($this->provider['input_shape_enum_values']);
}
public function getInputShapeDefaults(): array {
return $this->provider['input_shape_defaults'];
}
public function getOptionalInputShapeEnumValues(): array {
return $this->arrayToTaskProcessingEnumValues($this->provider['optional_input_shape_enum_values']);
}
public function getOptionalInputShapeDefaults(): array {
return $this->provider['optional_input_shape_defaults'];
}
public function getOutputShapeEnumValues(): array {
return $this->arrayToTaskProcessingEnumValues($this->provider['output_shape_enum_values']);
}
public function getOptionalOutputShapeEnumValues(): array {
return $this->arrayToTaskProcessingEnumValues($this->provider['optional_output_shape_enum_values']);
}
private function arrayToTaskProcessingEnumValues(array $enumValues): array {
$taskProcessingEnumValues = [];
foreach ($enumValues as $key => $value) {
$taskProcessingEnumValues[$key] = array_map(static fn (array $shape) => new ShapeEnumValue(...$shape), $value);
}
return $taskProcessingEnumValues;
}
};
}
public function resetCacheEnabled(): void {
$this->cache?->remove('/ex_task_processing_providers');
}
public function unregisterExAppTaskProcessingProviders(string $appId): int {
try {
$result = $this->mapper->removeAllByAppId($appId);
} catch (Exception) {
$result = -1;
}
$this->resetCacheEnabled();
return $result;
}
/**
* @param IRegistrationContext $context
*
* @return void
*/
public function registerExAppTaskProcessingCustomTaskTypes(IRegistrationContext $context): void {
$exAppsProviders = $this->getRegisteredTaskProcessingProviders();
foreach ($exAppsProviders as $exAppProvider) {
$customTaskType = $exAppProvider->getCustomTaskType();
if ($customTaskType === null) {
continue;
}
/** @var class-string<ITaskType> $className */
$className = '\\OCA\\AppAPI\\' . $exAppProvider->getAppId() . '\\' . $exAppProvider->getName() . '\\TaskType';
try {
$taskType = $this->getAnonymousTaskType(json_decode($customTaskType, true, 512, JSON_THROW_ON_ERROR));
} catch (JsonException $e) {
$this->logger->debug('Failed to register ExApp TaskProcessing custom task type', ['exAppId' => $exAppProvider->getAppId(), 'taskType' => $exAppProvider->getName(), 'exception' => $e]);
continue;
} catch (\Throwable) {
continue;
}
$context->registerService($className, function () use ($taskType) {
return $taskType;
});
$context->registerTaskProcessingTaskType($className);
}
}
private function getAnonymousTaskType(
array $customTaskType,
): ITaskType {
return new class($customTaskType) implements ITaskType {
public function __construct(
private readonly array $customTaskType,
) {
}
public function getId(): string {
return $this->customTaskType['id'];
}
public function getName(): string {
return $this->customTaskType['name'];
}
public function getDescription(): string {
return $this->customTaskType['description'];
}
public function getInputShape(): array {
return array_map(static fn (array $shape) => new ShapeDescriptor(
$shape['name'],
$shape['description'],
EShapeType::from($shape['type']),
), $this->customTaskType['input_shape']);
}
public function getOutputShape(): array {
return array_map(static fn (array $shape) => new ShapeDescriptor(
$shape['name'],
$shape['description'],
EShapeType::from($shape['type']),
), $this->customTaskType['output_shape']);
}
};
}
}