aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobin Appelman <robin@icewind.nl>2025-05-15 13:42:41 +0200
committerGitHub <noreply@github.com>2025-05-15 13:42:41 +0200
commit07fa9b9311a93c214383b5d0fda4a63503dd1dbb (patch)
tree19080b86bfe95bbd3cc07396bfabd841e5a31f10
parent2a01454fbf68376399c24f586b0ed49e312b07e3 (diff)
parentcfc41960388af724f4deb211e60a16a5a008855f (diff)
downloadnextcloud-server-07fa9b9311a93c214383b5d0fda4a63503dd1dbb.tar.gz
nextcloud-server-07fa9b9311a93c214383b5d0fda4a63503dd1dbb.zip
Merge pull request #51818 from nextcloud/info-storage-command
feat: add command to get basic storage info
-rw-r--r--core/Command/Info/FileUtils.php102
-rw-r--r--core/Command/Info/Storage.php49
-rw-r--r--core/Command/Info/Storages.php43
-rw-r--r--core/register_command.php4
-rw-r--r--lib/composer/composer/autoload_classmap.php2
-rw-r--r--lib/composer/composer/autoload_static.php2
6 files changed, 202 insertions, 0 deletions
diff --git a/core/Command/Info/FileUtils.php b/core/Command/Info/FileUtils.php
index 4de0fbd5faa..bc07535a289 100644
--- a/core/Command/Info/FileUtils.php
+++ b/core/Command/Info/FileUtils.php
@@ -14,6 +14,7 @@ use OCA\Files_External\Config\ExternalMountPoint;
use OCA\Files_Sharing\SharedMount;
use OCA\GroupFolders\Mount\GroupMountPoint;
use OCP\Constants;
+use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Config\IUserMountCache;
use OCP\Files\FileInfo;
use OCP\Files\Folder;
@@ -23,14 +24,19 @@ use OCP\Files\Mount\IMountPoint;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
+use OCP\IDBConnection;
use OCP\Share\IShare;
use OCP\Util;
use Symfony\Component\Console\Output\OutputInterface;
+/**
+ * @psalm-type StorageInfo array{numeric_id: int, id: string, available: bool, last_checked: ?\DateTime, files: int, mount_id: ?int}
+ */
class FileUtils {
public function __construct(
private IRootFolder $rootFolder,
private IUserMountCache $userMountCache,
+ private IDBConnection $connection,
) {
}
@@ -220,4 +226,100 @@ class FileUtils {
}
return $count;
}
+
+ public function getNumericStorageId(string $id): ?int {
+ if (is_numeric($id)) {
+ return (int)$id;
+ }
+ $query = $this->connection->getQueryBuilder();
+ $query->select('numeric_id')
+ ->from('storages')
+ ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
+ $result = $query->executeQuery()->fetchOne();
+ return $result ? (int)$result : null;
+ }
+
+ /**
+ * @param int|null $limit
+ * @return ?StorageInfo
+ * @throws \OCP\DB\Exception
+ */
+ public function getStorage(int $id): ?array {
+ $query = $this->connection->getQueryBuilder();
+ $query->select('numeric_id', 's.id', 'available', 'last_checked', 'mount_id')
+ ->selectAlias($query->func()->count('fileid'), 'files')
+ ->from('storages', 's')
+ ->innerJoin('s', 'filecache', 'f', $query->expr()->eq('f.storage', 's.numeric_id'))
+ ->leftJoin('s', 'mounts', 'm', $query->expr()->eq('s.numeric_id', 'm.storage_id'))
+ ->where($query->expr()->eq('s.numeric_id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
+ ->groupBy('s.numeric_id', 's.id', 's.available', 's.last_checked', 'mount_id');
+ $row = $query->executeQuery()->fetch();
+ if ($row) {
+ return [
+ 'numeric_id' => $row['numeric_id'],
+ 'id' => $row['id'],
+ 'files' => $row['files'],
+ 'available' => (bool)$row['available'],
+ 'last_checked' => $row['last_checked'] ? new \DateTime('@' . $row['last_checked']) : null,
+ 'mount_id' => $row['mount_id'],
+ ];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @param int|null $limit
+ * @return \Iterator<StorageInfo>
+ * @throws \OCP\DB\Exception
+ */
+ public function listStorages(?int $limit): \Iterator {
+ $query = $this->connection->getQueryBuilder();
+ $query->select('numeric_id', 's.id', 'available', 'last_checked', 'mount_id')
+ ->selectAlias($query->func()->count('fileid'), 'files')
+ ->from('storages', 's')
+ ->innerJoin('s', 'filecache', 'f', $query->expr()->eq('f.storage', 's.numeric_id'))
+ ->leftJoin('s', 'mounts', 'm', $query->expr()->eq('s.numeric_id', 'm.storage_id'))
+ ->groupBy('s.numeric_id', 's.id', 's.available', 's.last_checked', 'mount_id')
+ ->orderBy('files', 'DESC');
+ if ($limit !== null) {
+ $query->setMaxResults($limit);
+ }
+ $result = $query->executeQuery();
+ while ($row = $result->fetch()) {
+ yield [
+ 'numeric_id' => $row['numeric_id'],
+ 'id' => $row['id'],
+ 'files' => $row['files'],
+ 'available' => (bool)$row['available'],
+ 'last_checked' => $row['last_checked'] ? new \DateTime('@' . $row['last_checked']) : null,
+ 'mount_id' => $row['mount_id'],
+ ];
+ }
+ }
+
+ /**
+ * @param StorageInfo $storage
+ * @return array
+ */
+ public function formatStorage(array $storage): array {
+ return [
+ 'numeric_id' => $storage['numeric_id'],
+ 'id' => $storage['id'],
+ 'files' => $storage['files'],
+ 'available' => $storage['available'] ? 'true' : 'false',
+ 'last_checked' => $storage['last_checked']?->format(\DATE_ATOM),
+ 'external_mount_id' => $storage['mount_id'],
+ ];
+ }
+
+ /**
+ * @param \Iterator<StorageInfo> $storages
+ * @return \Iterator
+ */
+ public function formatStorages(\Iterator $storages): \Iterator {
+ foreach ($storages as $storage) {
+ yield $this->formatStorage($storage);
+ }
+ }
}
diff --git a/core/Command/Info/Storage.php b/core/Command/Info/Storage.php
new file mode 100644
index 00000000000..c1d0e1725ca
--- /dev/null
+++ b/core/Command/Info/Storage.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Info;
+
+use OC\Core\Command\Base;
+use OCP\IDBConnection;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Storage extends Base {
+ public function __construct(
+ private readonly IDBConnection $connection,
+ private readonly FileUtils $fileUtils,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('info:storage')
+ ->setDescription('Get information a single storage')
+ ->addArgument('storage', InputArgument::REQUIRED, 'Storage to get information for');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $storage = $input->getArgument('storage');
+ $storageId = $this->fileUtils->getNumericStorageId($storage);
+ if (!$storageId) {
+ $output->writeln('<error>No storage with id ' . $storage . ' found</error>');
+ return 1;
+ }
+
+ $info = $this->fileUtils->getStorage($storageId);
+ if (!$info) {
+ $output->writeln('<error>No storage with id ' . $storage . ' found</error>');
+ return 1;
+ }
+ $this->writeArrayInOutputFormat($input, $output, $this->fileUtils->formatStorage($info));
+ return 0;
+ }
+}
diff --git a/core/Command/Info/Storages.php b/core/Command/Info/Storages.php
new file mode 100644
index 00000000000..ff767a2ff5d
--- /dev/null
+++ b/core/Command/Info/Storages.php
@@ -0,0 +1,43 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Core\Command\Info;
+
+use OC\Core\Command\Base;
+use OCP\IDBConnection;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class Storages extends Base {
+ public function __construct(
+ private readonly IDBConnection $connection,
+ private readonly FileUtils $fileUtils,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ parent::configure();
+ $this
+ ->setName('info:storages')
+ ->setDescription('List storages ordered by the number of files')
+ ->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'Number of storages to display', 25)
+ ->addOption('all', 'a', InputOption::VALUE_NONE, 'Display all storages');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $count = (int)$input->getOption('count');
+ $all = $input->getOption('all');
+
+ $limit = $all ? null : $count;
+ $storages = $this->fileUtils->listStorages($limit);
+ $this->writeStreamingTableInOutputFormat($input, $output, $this->fileUtils->formatStorages($storages), 100);
+ return 0;
+ }
+}
diff --git a/core/register_command.php b/core/register_command.php
index a276cd58bfd..72a4b70f059 100644
--- a/core/register_command.php
+++ b/core/register_command.php
@@ -53,6 +53,8 @@ use OC\Core\Command\Group\AddUser;
use OC\Core\Command\Group\RemoveUser;
use OC\Core\Command\Info\File;
use OC\Core\Command\Info\Space;
+use OC\Core\Command\Info\Storage;
+use OC\Core\Command\Info\Storages;
use OC\Core\Command\Integrity\CheckApp;
use OC\Core\Command\Integrity\CheckCore;
use OC\Core\Command\Integrity\SignApp;
@@ -144,6 +146,8 @@ if ($config->getSystemValueBool('installed', false)) {
$application->add(Server::get(File::class));
$application->add(Server::get(Space::class));
+ $application->add(Server::get(Storage::class));
+ $application->add(Server::get(Storages::class));
$application->add(Server::get(ConvertType::class));
$application->add(Server::get(ConvertMysqlToMB4::class));
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 856afbe5677..b755234614a 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -1266,6 +1266,8 @@ return array(
'OC\\Core\\Command\\Info\\File' => $baseDir . '/core/Command/Info/File.php',
'OC\\Core\\Command\\Info\\FileUtils' => $baseDir . '/core/Command/Info/FileUtils.php',
'OC\\Core\\Command\\Info\\Space' => $baseDir . '/core/Command/Info/Space.php',
+ 'OC\\Core\\Command\\Info\\Storage' => $baseDir . '/core/Command/Info/Storage.php',
+ 'OC\\Core\\Command\\Info\\Storages' => $baseDir . '/core/Command/Info/Storages.php',
'OC\\Core\\Command\\Integrity\\CheckApp' => $baseDir . '/core/Command/Integrity/CheckApp.php',
'OC\\Core\\Command\\Integrity\\CheckCore' => $baseDir . '/core/Command/Integrity/CheckCore.php',
'OC\\Core\\Command\\Integrity\\SignApp' => $baseDir . '/core/Command/Integrity/SignApp.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index fed9723421e..26cd9ee0293 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -1307,6 +1307,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Command\\Info\\File' => __DIR__ . '/../../..' . '/core/Command/Info/File.php',
'OC\\Core\\Command\\Info\\FileUtils' => __DIR__ . '/../../..' . '/core/Command/Info/FileUtils.php',
'OC\\Core\\Command\\Info\\Space' => __DIR__ . '/../../..' . '/core/Command/Info/Space.php',
+ 'OC\\Core\\Command\\Info\\Storage' => __DIR__ . '/../../..' . '/core/Command/Info/Storage.php',
+ 'OC\\Core\\Command\\Info\\Storages' => __DIR__ . '/../../..' . '/core/Command/Info/Storages.php',
'OC\\Core\\Command\\Integrity\\CheckApp' => __DIR__ . '/../../..' . '/core/Command/Integrity/CheckApp.php',
'OC\\Core\\Command\\Integrity\\CheckCore' => __DIR__ . '/../../..' . '/core/Command/Integrity/CheckCore.php',
'OC\\Core\\Command\\Integrity\\SignApp' => __DIR__ . '/../../..' . '/core/Command/Integrity/SignApp.php',