diff --git a/core/Command/Config/System/CastHelper.php b/core/Command/Config/System/CastHelper.php new file mode 100644 index 0000000000000..abbcacf92110e --- /dev/null +++ b/core/Command/Config/System/CastHelper.php @@ -0,0 +1,76 @@ + (int) $value, + 'readable-value' => 'integer ' . (int) $value, + ]; + + case 'double': + case 'float': + if (!is_numeric($value)) { + throw new \InvalidArgumentException('Non-numeric value specified'); + } + return [ + 'value' => (float) $value, + 'readable-value' => 'double ' . (float) $value, + ]; + + case 'boolean': + case 'bool': + $value = strtolower($value); + return match ($value) { + 'true' => [ + 'value' => true, + 'readable-value' => 'boolean ' . $value, + ], + 'false' => [ + 'value' => false, + 'readable-value' => 'boolean ' . $value, + ], + default => throw new \InvalidArgumentException('Unable to parse value as boolean'), + }; + + case 'null': + return [ + 'value' => null, + 'readable-value' => 'null', + ]; + + case 'string': + $value = (string) $value; + return [ + 'value' => $value, + 'readable-value' => ($value === '') ? 'empty string' : 'string ' . $value, + ]; + + case 'json': + $value = json_decode($value, true); + return [ + 'value' => $value, + 'readable-value' => 'json ' . json_encode($value), + ]; + + default: + throw new \InvalidArgumentException('Invalid type'); + } + } +} diff --git a/core/Command/Config/System/SetConfig.php b/core/Command/Config/System/SetConfig.php index 4ea3383695641..21cdb6666b1cf 100644 --- a/core/Command/Config/System/SetConfig.php +++ b/core/Command/Config/System/SetConfig.php @@ -17,6 +17,7 @@ class SetConfig extends Base { public function __construct( SystemConfig $systemConfig, + private CastHelper $castHelper, ) { parent::__construct($systemConfig); } @@ -57,7 +58,7 @@ protected function configure() { protected function execute(InputInterface $input, OutputInterface $output): int { $configNames = $input->getArgument('name'); $configName = $configNames[0]; - $configValue = $this->castValue($input->getOption('value'), $input->getOption('type')); + $configValue = $this->castHelper->castValue($input->getOption('value'), $input->getOption('type')); $updateOnly = $input->getOption('update-only'); if (count($configNames) > 1) { diff --git a/core/Command/Memcache/DistributedClear.php b/core/Command/Memcache/DistributedClear.php new file mode 100644 index 0000000000000..424f21f1e8124 --- /dev/null +++ b/core/Command/Memcache/DistributedClear.php @@ -0,0 +1,47 @@ +setName('memcache:distributed:clear') + ->setDescription('Clear values from the distributed memcache') + ->addOption('prefix', null, InputOption::VALUE_REQUIRED, 'Only remove keys matching the prefix'); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $cache = $this->cacheFactory->createDistributed(); + $prefix = $input->getOption('prefix'); + if ($cache->clear($prefix)) { + if ($prefix) { + $output->writeln('Distributed cache matching prefix ' . $prefix . ' cleared'); + } else { + $output->writeln('Distributed cache cleared'); + } + return 0; + } else { + $output->writeln('Failed to clear cache'); + return 1; + } + } +} diff --git a/core/Command/Memcache/DistributedDelete.php b/core/Command/Memcache/DistributedDelete.php new file mode 100644 index 0000000000000..ae0855acb03ea --- /dev/null +++ b/core/Command/Memcache/DistributedDelete.php @@ -0,0 +1,43 @@ +setName('memcache:distributed:delete') + ->setDescription('Delete a value in the distributed memcache') + ->addArgument('key', InputArgument::REQUIRED, 'The key to delete'); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $cache = $this->cacheFactory->createDistributed(); + $key = $input->getArgument('key'); + if ($cache->remove($key)) { + $output->writeln('Distributed cache key ' . $key . ' deleted'); + return 0; + } else { + $output->writeln('Failed to delete cache key ' . $key . ''); + return 1; + } + } +} diff --git a/core/Command/Memcache/DistributedGet.php b/core/Command/Memcache/DistributedGet.php new file mode 100644 index 0000000000000..bf1b00d312d81 --- /dev/null +++ b/core/Command/Memcache/DistributedGet.php @@ -0,0 +1,40 @@ +setName('memcache:distributed:get') + ->setDescription('Get a value from the distributed memcache') + ->addArgument('key', InputArgument::REQUIRED, 'The key to retrieve'); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $cache = $this->cacheFactory->createDistributed(); + $key = $input->getArgument('key'); + + $value = $cache->get($key); + $this->writeMixedInOutputFormat($input, $output, $value); + return 0; + } +} diff --git a/core/Command/Memcache/DistributedSet.php b/core/Command/Memcache/DistributedSet.php new file mode 100644 index 0000000000000..0f31c22f730d1 --- /dev/null +++ b/core/Command/Memcache/DistributedSet.php @@ -0,0 +1,57 @@ +setName('memcache:distributed:set') + ->setDescription('Set a value in the distributed memcache') + ->addArgument('key', InputArgument::REQUIRED, 'The key to set') + ->addArgument('value', InputArgument::REQUIRED, 'The value to set') + ->addOption( + 'type', + null, + InputOption::VALUE_REQUIRED, + 'Value type [string, integer, float, boolean, json, null]', + 'string' + ); + parent::configure(); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $cache = $this->cacheFactory->createDistributed(); + $key = $input->getArgument('key'); + $value = $input->getArgument('value'); + $type = $input->getOption('type'); + ['value' => $value, 'readable-value' => $readable] = $this->castHelper->castValue($value, $type); + if ($cache->set($key, $value)) { + $output->writeln('Distributed cache key ' . $key . ' set to ' . $readable . ''); + return 0; + } else { + $output->writeln('Failed to set cache key ' . $key . ''); + return 1; + } + } +} diff --git a/core/register_command.php b/core/register_command.php index f898d91df70b4..f3b559cba343c 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -147,6 +147,10 @@ $application->add(Server::get(Command\TaskProcessing\ListCommand::class)); $application->add(Server::get(Command\TaskProcessing\Statistics::class)); + $application->add(Server::get(Command\Memcache\DistributedClear::class)); + $application->add(Server::get(Command\Memcache\DistributedDelete::class)); + $application->add(Server::get(Command\Memcache\DistributedGet::class)); + $application->add(Server::get(Command\Memcache\DistributedSet::class)); } else { $application->add(Server::get(Command\Maintenance\Install::class)); } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index fa4a5528aeb56..e638b01ccaa35 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1157,6 +1157,7 @@ 'OC\\Core\\Command\\Config\\Import' => $baseDir . '/core/Command/Config/Import.php', 'OC\\Core\\Command\\Config\\ListConfigs' => $baseDir . '/core/Command/Config/ListConfigs.php', 'OC\\Core\\Command\\Config\\System\\Base' => $baseDir . '/core/Command/Config/System/Base.php', + 'OC\\Core\\Command\\Config\\System\\CastHelper' => $baseDir . '/core/Command/Config/System/CastHelper.php', 'OC\\Core\\Command\\Config\\System\\DeleteConfig' => $baseDir . '/core/Command/Config/System/DeleteConfig.php', 'OC\\Core\\Command\\Config\\System\\GetConfig' => $baseDir . '/core/Command/Config/System/GetConfig.php', 'OC\\Core\\Command\\Config\\System\\SetConfig' => $baseDir . '/core/Command/Config/System/SetConfig.php', @@ -1213,6 +1214,10 @@ 'OC\\Core\\Command\\Maintenance\\RepairShareOwnership' => $baseDir . '/core/Command/Maintenance/RepairShareOwnership.php', 'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => $baseDir . '/core/Command/Maintenance/UpdateHtaccess.php', 'OC\\Core\\Command\\Maintenance\\UpdateTheme' => $baseDir . '/core/Command/Maintenance/UpdateTheme.php', + 'OC\\Core\\Command\\Memcache\\DistributedClear' => $baseDir . '/core/Command/Memcache/DistributedClear.php', + 'OC\\Core\\Command\\Memcache\\DistributedDelete' => $baseDir . '/core/Command/Memcache/DistributedDelete.php', + 'OC\\Core\\Command\\Memcache\\DistributedGet' => $baseDir . '/core/Command/Memcache/DistributedGet.php', + 'OC\\Core\\Command\\Memcache\\DistributedSet' => $baseDir . '/core/Command/Memcache/DistributedSet.php', 'OC\\Core\\Command\\Preview\\Generate' => $baseDir . '/core/Command/Preview/Generate.php', 'OC\\Core\\Command\\Preview\\Repair' => $baseDir . '/core/Command/Preview/Repair.php', 'OC\\Core\\Command\\Preview\\ResetRenderedTexts' => $baseDir . '/core/Command/Preview/ResetRenderedTexts.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 34d6c08a8aad6..aed7834273691 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -1190,6 +1190,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Command\\Config\\Import' => __DIR__ . '/../../..' . '/core/Command/Config/Import.php', 'OC\\Core\\Command\\Config\\ListConfigs' => __DIR__ . '/../../..' . '/core/Command/Config/ListConfigs.php', 'OC\\Core\\Command\\Config\\System\\Base' => __DIR__ . '/../../..' . '/core/Command/Config/System/Base.php', + 'OC\\Core\\Command\\Config\\System\\CastHelper' => __DIR__ . '/../../..' . '/core/Command/Config/System/CastHelper.php', 'OC\\Core\\Command\\Config\\System\\DeleteConfig' => __DIR__ . '/../../..' . '/core/Command/Config/System/DeleteConfig.php', 'OC\\Core\\Command\\Config\\System\\GetConfig' => __DIR__ . '/../../..' . '/core/Command/Config/System/GetConfig.php', 'OC\\Core\\Command\\Config\\System\\SetConfig' => __DIR__ . '/../../..' . '/core/Command/Config/System/SetConfig.php', @@ -1246,6 +1247,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Core\\Command\\Maintenance\\RepairShareOwnership' => __DIR__ . '/../../..' . '/core/Command/Maintenance/RepairShareOwnership.php', 'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateHtaccess.php', 'OC\\Core\\Command\\Maintenance\\UpdateTheme' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateTheme.php', + 'OC\\Core\\Command\\Memcache\\DistributedClear' => __DIR__ . '/../../..' . '/core/Command/Memcache/DistributedClear.php', + 'OC\\Core\\Command\\Memcache\\DistributedDelete' => __DIR__ . '/../../..' . '/core/Command/Memcache/DistributedDelete.php', + 'OC\\Core\\Command\\Memcache\\DistributedGet' => __DIR__ . '/../../..' . '/core/Command/Memcache/DistributedGet.php', + 'OC\\Core\\Command\\Memcache\\DistributedSet' => __DIR__ . '/../../..' . '/core/Command/Memcache/DistributedSet.php', 'OC\\Core\\Command\\Preview\\Generate' => __DIR__ . '/../../..' . '/core/Command/Preview/Generate.php', 'OC\\Core\\Command\\Preview\\Repair' => __DIR__ . '/../../..' . '/core/Command/Preview/Repair.php', 'OC\\Core\\Command\\Preview\\ResetRenderedTexts' => __DIR__ . '/../../..' . '/core/Command/Preview/ResetRenderedTexts.php', diff --git a/tests/Core/Command/Config/System/CastHelperTest.php b/tests/Core/Command/Config/System/CastHelperTest.php new file mode 100644 index 0000000000000..e31f74b4a55ac --- /dev/null +++ b/tests/Core/Command/Config/System/CastHelperTest.php @@ -0,0 +1,70 @@ +castHelper = new CastHelper(); + } + + public static function castValueProvider(): array { + return [ + [null, 'string', ['value' => '', 'readable-value' => 'empty string']], + + ['abc', 'string', ['value' => 'abc', 'readable-value' => 'string abc']], + + ['123', 'integer', ['value' => 123, 'readable-value' => 'integer 123']], + ['456', 'int', ['value' => 456, 'readable-value' => 'integer 456']], + + ['2.25', 'double', ['value' => 2.25, 'readable-value' => 'double 2.25']], + ['0.5', 'float', ['value' => 0.5, 'readable-value' => 'double 0.5']], + + ['', 'null', ['value' => null, 'readable-value' => 'null']], + + ['true', 'boolean', ['value' => true, 'readable-value' => 'boolean true']], + ['false', 'bool', ['value' => false, 'readable-value' => 'boolean false']], + ]; + } + + /** + * @dataProvider castValueProvider + */ + public function testCastValue($value, $type, $expectedValue): void { + $this->assertSame( + $expectedValue, + $this->castHelper->castValue($value, $type) + ); + } + + public static function castValueInvalidProvider(): array { + return [ + ['123', 'foobar'], + + [null, 'integer'], + ['abc', 'integer'], + ['76ggg', 'double'], + ['true', 'float'], + ['foobar', 'boolean'], + ]; + } + + /** + * @dataProvider castValueInvalidProvider + */ + public function testCastValueInvalid($value, $type): void { + $this->expectException(\InvalidArgumentException::class); + + $this->castHelper->castValue($value, $type); + } +} diff --git a/tests/Core/Command/Config/System/SetConfigTest.php b/tests/Core/Command/Config/System/SetConfigTest.php index 0847a93685ee7..d7ced242eb771 100644 --- a/tests/Core/Command/Config/System/SetConfigTest.php +++ b/tests/Core/Command/Config/System/SetConfigTest.php @@ -8,6 +8,7 @@ namespace Tests\Core\Command\Config\System; +use OC\Core\Command\Config\System\CastHelper; use OC\Core\Command\Config\System\SetConfig; use OC\SystemConfig; use Symfony\Component\Console\Input\InputInterface; @@ -36,7 +37,7 @@ protected function setUp(): void { $this->consoleOutput = $this->getMockBuilder(OutputInterface::class)->getMock(); /** @var \OC\SystemConfig $systemConfig */ - $this->command = new SetConfig($systemConfig); + $this->command = new SetConfig($systemConfig, new CastHelper()); }