mirror of
https://github.com/nextcloud/app_api.git
synced 2026-01-13 20:19:21 +00:00
269 lines
7.6 KiB
PHP
269 lines
7.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace OCA\AppAPI\Service;
|
|
|
|
use OCA\AppAPI\AppInfo\Application;
|
|
use OCA\AppAPI\Db\Console\ExAppOccCommand;
|
|
use OCA\AppAPI\Db\Console\ExAppOccCommandMapper;
|
|
use OCA\AppAPI\PublicFunctions;
|
|
use OCP\AppFramework\Http;
|
|
use OCP\DB\Exception;
|
|
use OCP\ICache;
|
|
use OCP\ICacheFactory;
|
|
use Psr\Container\ContainerInterface;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Component\Console\Command\Command;
|
|
use Symfony\Component\Console\Input\InputArgument;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
use Symfony\Component\Console\Input\InputOption;
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
|
|
class ExAppOccService {
|
|
|
|
private ?ICache $cache = null;
|
|
|
|
public function __construct(
|
|
private readonly LoggerInterface $logger,
|
|
private readonly ExAppOccCommandMapper $mapper,
|
|
ICacheFactory $cacheFactory,
|
|
) {
|
|
if ($cacheFactory->isAvailable()) {
|
|
$this->cache = $cacheFactory->createDistributed(Application::APP_ID . '/ex_occ_commands');
|
|
}
|
|
}
|
|
|
|
public function registerCommand(
|
|
string $appId,
|
|
string $name,
|
|
string $description,
|
|
int $hidden,
|
|
array $arguments,
|
|
array $options,
|
|
array $usages,
|
|
string $executeHandler
|
|
): ?ExAppOccCommand {
|
|
$occCommandEntry = $this->getOccCommand($appId, $name);
|
|
try {
|
|
$newOccCommandEntry = new ExAppOccCommand([
|
|
'appid' => $appId,
|
|
'name' => $name,
|
|
'description' => $description,
|
|
'hidden' => $hidden,
|
|
'arguments' => $arguments,
|
|
'options' => $options,
|
|
'usages' => $usages,
|
|
'execute_handler' => ltrim($executeHandler, '/'),
|
|
]);
|
|
if ($occCommandEntry !== null) {
|
|
$newOccCommandEntry->setId($occCommandEntry->getId());
|
|
}
|
|
$occCommandEntry = $this->mapper->insertOrUpdate($newOccCommandEntry);
|
|
$this->resetCacheEnabled();
|
|
} catch (Exception $e) {
|
|
$this->logger->error(
|
|
sprintf('Failed to register ExApp OCC command for %s. Error: %s', $appId, $e->getMessage()), ['exception' => $e]
|
|
);
|
|
return null;
|
|
}
|
|
return $occCommandEntry;
|
|
}
|
|
|
|
public function unregisterCommand(string $appId, string $name): bool {
|
|
if (!$this->mapper->removeByAppIdOccName($appId, $name)) {
|
|
return false;
|
|
}
|
|
$this->resetCacheEnabled();
|
|
return true;
|
|
}
|
|
|
|
public function getOccCommand(string $appId, string $name): ?ExAppOccCommand {
|
|
foreach ($this->getOccCommands() as $occCommand) {
|
|
if (($occCommand->getAppid() === $appId) && ($occCommand->getName() === $name)) {
|
|
return $occCommand;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get list of registered ExApp OCC Commands (only for enabled ExApps)
|
|
*
|
|
* @return ExAppOccCommand[]
|
|
*/
|
|
public function getOccCommands(): array {
|
|
try {
|
|
$cacheKey = '/ex_occ_commands';
|
|
$records = $this->cache?->get($cacheKey);
|
|
if (!is_array($records)) {
|
|
$records = $this->mapper->findAllEnabled();
|
|
$this->cache?->set($cacheKey, $records);
|
|
}
|
|
return array_map(function ($record) {
|
|
return new ExAppOccCommand($record);
|
|
}, $records);
|
|
} catch (Exception) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
public function buildCommand(ExAppOccCommand $occCommand, ContainerInterface $container): Command {
|
|
return new class($occCommand, $container) extends Command {
|
|
private LoggerInterface $logger;
|
|
private PublicFunctions $service;
|
|
|
|
public function __construct(
|
|
private ExAppOccCommand $occCommand,
|
|
private ContainerInterface $container
|
|
) {
|
|
parent::__construct();
|
|
|
|
$this->logger = $this->container->get(LoggerInterface::class);
|
|
$this->service = $this->container->get(PublicFunctions::class);
|
|
}
|
|
|
|
protected function configure() {
|
|
$this->setName($this->occCommand->getName());
|
|
$this->setDescription($this->occCommand->getDescription());
|
|
$this->setHidden(filter_var($this->occCommand->getHidden(), FILTER_VALIDATE_BOOLEAN));
|
|
foreach ($this->occCommand->getArguments() as $argument) {
|
|
$this->addArgument(
|
|
$argument['name'],
|
|
$this->buildArgumentMode($argument['mode']),
|
|
$argument['description'],
|
|
in_array($argument['mode'], ['optional', 'array']) ? $argument['default'] : null,
|
|
);
|
|
}
|
|
foreach ($this->occCommand->getOptions() as $option) {
|
|
$this->addOption(
|
|
$option['name'],
|
|
$option['shortcut'] ?? null,
|
|
$this->buildOptionMode($option['mode']),
|
|
$option['description'],
|
|
$this->buildOptionMode($option['mode']) !== InputOption::VALUE_NONE
|
|
? $option['default'] ?? null
|
|
: null
|
|
);
|
|
}
|
|
foreach ($this->occCommand->getUsages() as $usage) {
|
|
$this->addUsage($usage);
|
|
}
|
|
}
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
|
if (count($this->occCommand->getArguments()) > 0) {
|
|
$arguments = [];
|
|
foreach ($this->occCommand->getArguments() as $argument) {
|
|
$arguments[$argument['name']] = $input->getArgument($argument['name']);
|
|
}
|
|
} else {
|
|
$arguments = null;
|
|
}
|
|
if (count($this->occCommand->getOptions()) > 0) {
|
|
$options = [];
|
|
foreach ($this->occCommand->getOptions() as $option) {
|
|
$options[$option['name']] = $input->getOption($option['name']);
|
|
}
|
|
} else {
|
|
$options = null;
|
|
}
|
|
|
|
$executeHandler = $this->occCommand->getExecuteHandler();
|
|
$response = $this->service->exAppRequest($this->occCommand->getAppid(), $executeHandler,
|
|
params: [
|
|
'occ' => [
|
|
'arguments' => $arguments,
|
|
'options' => $options,
|
|
],
|
|
],
|
|
options: [
|
|
'stream' => true,
|
|
'timeout' => 0,
|
|
]
|
|
);
|
|
|
|
if (is_array($response) && isset($response['error'])) {
|
|
$output->writeln(sprintf('[%s] command executeHandler failed. Error: %s', $this->occCommand->getName(), $response['error']));
|
|
$this->logger->error(sprintf('[%s] command executeHandler failed. Error: %s', $this->occCommand->getName(), $response['error']), [
|
|
'app' => $this->occCommand->getAppid(),
|
|
]);
|
|
return 1;
|
|
}
|
|
|
|
if ($response->getStatusCode() !== Http::STATUS_OK) {
|
|
$output->writeln(sprintf('[%s] command executeHandler failed', $this->occCommand->getName()));
|
|
$this->logger->error(sprintf('[%s] command executeHandler failed', $this->occCommand->getName()), [
|
|
'app' => $this->occCommand->getAppid(),
|
|
]);
|
|
return 1;
|
|
}
|
|
|
|
$body = $response->getBody();
|
|
if (is_resource($body)) {
|
|
while (!feof($body)) {
|
|
$output->write(fread($body, 1024));
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private function buildArgumentMode(string $mode): int {
|
|
$modes = explode(',', $mode);
|
|
$argumentMode = 0;
|
|
foreach ($modes as $mode) {
|
|
$argumentMode |= $this->_buildArgumentMode($mode);
|
|
}
|
|
return $argumentMode;
|
|
}
|
|
|
|
private function _buildArgumentMode(string $mode): int {
|
|
if ($mode === 'required') {
|
|
return InputArgument::REQUIRED;
|
|
}
|
|
if ($mode === 'optional') {
|
|
return InputArgument::OPTIONAL;
|
|
}
|
|
if ($mode === 'array') {
|
|
return InputArgument::IS_ARRAY;
|
|
}
|
|
return InputArgument::OPTIONAL;
|
|
}
|
|
|
|
private function buildOptionMode(string $mode): int {
|
|
if ($mode === 'required') {
|
|
return InputOption::VALUE_REQUIRED;
|
|
}
|
|
if ($mode === 'optional') {
|
|
return InputOption::VALUE_OPTIONAL;
|
|
}
|
|
if ($mode === 'none') {
|
|
return InputOption::VALUE_NONE;
|
|
}
|
|
if ($mode === 'array') {
|
|
return InputOption::VALUE_IS_ARRAY;
|
|
}
|
|
if ($mode === 'negatable') {
|
|
return InputOption::VALUE_NEGATABLE;
|
|
}
|
|
return InputOption::VALUE_NONE;
|
|
}
|
|
};
|
|
}
|
|
|
|
public function unregisterExAppOccCommands(string $appId): int {
|
|
try {
|
|
$result = $this->mapper->removeAllByAppId($appId);
|
|
} catch (Exception) {
|
|
$result = -1;
|
|
}
|
|
$this->resetCacheEnabled();
|
|
return $result;
|
|
}
|
|
|
|
public function resetCacheEnabled(): void {
|
|
$this->cache?->remove('/ex_occ_commands');
|
|
}
|
|
}
|