diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml
index f3da2057f6523..41904932bcb26 100644
--- a/apps/files/appinfo/info.xml
+++ b/apps/files/appinfo/info.xml
@@ -49,6 +49,7 @@
OCA\Files\Command\Object\Delete
OCA\Files\Command\Object\Get
OCA\Files\Command\Object\Put
+ OCA\Files\Command\ListFiles
diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php
index 68cdabb3dcdab..7a02936897769 100644
--- a/apps/files/composer/composer/autoload_classmap.php
+++ b/apps/files/composer/composer/autoload_classmap.php
@@ -31,6 +31,7 @@
'OCA\\Files\\Command\\Delete' => $baseDir . '/../lib/Command/Delete.php',
'OCA\\Files\\Command\\DeleteOrphanedFiles' => $baseDir . '/../lib/Command/DeleteOrphanedFiles.php',
'OCA\\Files\\Command\\Get' => $baseDir . '/../lib/Command/Get.php',
+ 'OCA\\Files\\Command\\ListFiles' => $baseDir . '/../lib/Command/ListFiles.php',
'OCA\\Files\\Command\\Move' => $baseDir . '/../lib/Command/Move.php',
'OCA\\Files\\Command\\Object\\Delete' => $baseDir . '/../lib/Command/Object/Delete.php',
'OCA\\Files\\Command\\Object\\Get' => $baseDir . '/../lib/Command/Object/Get.php',
diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php
index ca88e773e4aa5..53fc7185c9fc5 100644
--- a/apps/files/composer/composer/autoload_static.php
+++ b/apps/files/composer/composer/autoload_static.php
@@ -46,6 +46,7 @@ class ComposerStaticInitFiles
'OCA\\Files\\Command\\Delete' => __DIR__ . '/..' . '/../lib/Command/Delete.php',
'OCA\\Files\\Command\\DeleteOrphanedFiles' => __DIR__ . '/..' . '/../lib/Command/DeleteOrphanedFiles.php',
'OCA\\Files\\Command\\Get' => __DIR__ . '/..' . '/../lib/Command/Get.php',
+ 'OCA\\Files\\Command\\ListFiles' => __DIR__ . '/..' . '/../lib/Command/ListFiles.php',
'OCA\\Files\\Command\\Move' => __DIR__ . '/..' . '/../lib/Command/Move.php',
'OCA\\Files\\Command\\Object\\Delete' => __DIR__ . '/..' . '/../lib/Command/Object/Delete.php',
'OCA\\Files\\Command\\Object\\Get' => __DIR__ . '/..' . '/../lib/Command/Object/Get.php',
diff --git a/apps/files/lib/Command/ListFiles.php b/apps/files/lib/Command/ListFiles.php
new file mode 100644
index 0000000000000..1a96d7a63226b
--- /dev/null
+++ b/apps/files/lib/Command/ListFiles.php
@@ -0,0 +1,289 @@
+setName("files:list")
+ ->setDescription("List files of the user and filter by path, type, size optionally")
+ ->addArgument(
+ "user_id",
+ InputArgument::REQUIRED,
+ 'List the files and folder belonging to the user, eg occ files:list admin, the user_id being a required argument'
+ )
+ ->addOption("path", "", InputArgument::OPTIONAL, "List files inside a particular path of the user, if not mentioned list from user's root directory")
+ ->addOption("type", "", InputArgument::OPTIONAL, "Filter by type like application, image, video etc")
+ ->addOption(
+ "minSize",
+ "0",
+ InputArgument::OPTIONAL,
+ "Filter by min size"
+ )
+ ->addOption(
+ "maxSize",
+ "0",
+ InputArgument::OPTIONAL,
+ "Filter by max size"
+ )
+ ->addOption(
+ "sort",
+ "name",
+ InputArgument::OPTIONAL,
+ "Sort by name, path, size, owner, type, perm, created-at"
+ )
+ ->addOption("order", "ASC", InputArgument::OPTIONAL, "Order is either ASC or DESC");
+ }
+
+ private function getNodeInfo(Node $node): array {
+ $nodeInfo = [
+ "name" => $node->getName(),
+ "size" => \OCP\Util::humanFileSize($node->getSize()),
+ "realSize" => $node->getSize(),
+ "perm" => $node->getPermissions(),
+ "owner" => $node->getOwner()?->getDisplayName(),
+ "created-at" => $node->getCreationTime(),
+ "type" => $node->getMimePart(),
+ ];
+ if($node->getMimetype() == FileInfo::MIMETYPE_FOLDER) {
+ $nodeInfo['type'] = 'directory';
+ }
+
+ return $nodeInfo;
+ }
+
+ protected function listFiles(
+ string $user,
+ OutputInterface $output,
+ ?string $path = "",
+ ?string $type = "",
+ ?int $minSize = 0,
+ ?int $maxSize = 0
+ ): void {
+ try {
+ $userFolder = $this->rootFolder->getUserFolder($user);
+ /** @var Folder $pathList **/
+ $pathList = $userFolder->get('/' . $path);
+
+ $files = $pathList->getDirectoryListing();
+ foreach ($files as $file) {
+ /** @var Node $fileNode */
+ $fileNode = $file;
+ $includeType = $includeMin = $includeMax = true;
+ $nodeInfo = $this->getNodeInfo($fileNode);
+ if ($type != "" && $type !== $nodeInfo['type']) {
+ $includeType = false;
+ }
+ if ($minSize > 0) {
+ $includeMin = $file->getSize() >= $minSize;
+ }
+ if ($maxSize > 0) {
+ $includeMax = $file->getSize() <= $maxSize;
+ }
+ if ($file instanceof File) {
+ if ($includeType && $includeMin && $includeMax) {
+ $this->fileInfo[] = $nodeInfo;
+ }
+ } elseif ($file instanceof Folder) {
+ if ($includeType && $includeMin && $includeMax) {
+ $this->dirInfo[] = $nodeInfo;
+ }
+ }
+ }
+ } catch (ForbiddenException $e) {
+ $output->writeln(
+ "Home storage for user $user not writable or 'files' subdirectory missing"
+ );
+ $output->writeln(" " . $e->getMessage());
+ $output->writeln(
+ 'Make sure you\'re running the list command only as the user the web server runs as'
+ );
+ } catch (InterruptedException $e) {
+ # exit the function if ctrl-c has been pressed
+ $output->writeln("Interrupted by user");
+ } catch (NotFoundException $e) {
+ $output->writeln(
+ "Path not found: " . $e->getMessage() . ""
+ );
+ } catch (\Exception $e) {
+ $output->writeln(
+ "Exception during list: " . $e->getMessage() . ""
+ );
+ $output->writeln("" . $e->getTraceAsString() . "");
+ }
+ }
+
+ protected function execute(
+ InputInterface $input,
+ OutputInterface $output
+ ): int {
+ $user = $input->getArgument("user_id");
+ $this->initTools($output);
+
+ if ($this->userManager->userExists($user)) {
+ $output->writeln(
+ "Starting list for user ($user)"
+ );
+ $this->listFiles(
+ $user,
+ $output,
+ (string) $input->getOption("path") ? $input->getOption("path") : '',
+ $input->getOption("type"),
+ (int) $input->getOption("minSize"),
+ (int) $input->getOption("maxSize")
+ );
+ } else {
+ $output->writeln(
+ "Unknown user $user"
+ );
+ $output->writeln("", OutputInterface::VERBOSITY_VERBOSE);
+ return self::FAILURE;
+ }
+
+ $this->presentStats($input, $output);
+ return self::SUCCESS;
+ }
+
+ /**
+ * Initialises some useful tools for the Command
+ */
+ protected function initTools(OutputInterface $output): void {
+ // Convert PHP errors to exceptions
+ set_error_handler(
+ fn (
+ int $severity,
+ string $message,
+ string $file,
+ int $line
+ ): bool => $this->exceptionErrorHandler(
+ $output,
+ $severity,
+ $message,
+ $file,
+ $line
+ ),
+ E_ALL
+ );
+ }
+
+ /**
+ * Processes PHP errors in order to be able to show them in the output
+ *
+ * @see https://www.php.net/manual/en/function.set-error-handler.php
+ *
+ * @param int $severity the level of the error raised
+ * @param string $message
+ * @param string $file the filename that the error was raised in
+ * @param int $line the line number the error was raised
+ */
+ public function exceptionErrorHandler(
+ OutputInterface $output,
+ int $severity,
+ string $message,
+ string $file,
+ int $line
+ ): bool {
+ if ($severity === E_DEPRECATED || $severity === E_USER_DEPRECATED) {
+ // Do not show deprecation warnings
+ return false;
+ }
+ $e = new \ErrorException($message, 0, $severity, $file, $line);
+ $output->writeln(
+ "Error during list: " . $e->getMessage() . ""
+ );
+ $output->writeln(
+ "" . $e->getTraceAsString() . "",
+ OutputInterface::VERBOSITY_VERY_VERBOSE
+ );
+ return true;
+ }
+
+ protected function presentStats(
+ InputInterface $input,
+ OutputInterface $output
+ ): void {
+ $rows = [];
+ $fileInfo = $this->fileInfo[0] ?? [];
+ $sortKey = array_key_exists($input->getOption("sort"), $fileInfo)
+ ? $input->getOption("sort")
+ : "";
+ if($sortKey == 'size') {
+ $sortKey = 'realSize';
+ }
+ $order = $input->getOption("order") == "ASC" ? SORT_ASC : SORT_DESC;
+ $fileArr = array_column($this->fileInfo, $sortKey);
+ $dirArr = array_column($this->dirInfo, $sortKey);
+ if($sortKey != '') {
+ array_multisort(
+ $fileArr,
+ $order,
+ SORT_NATURAL | SORT_FLAG_CASE,
+ $this->fileInfo
+ );
+ array_multisort(
+ $dirArr,
+ $order,
+ SORT_NATURAL | SORT_FLAG_CASE,
+ $this->dirInfo
+ );
+ }
+ foreach ($this->fileInfo as $k => $item) {
+ $rows[$k] = [
+ "Permission" => $item["perm"],
+ "Size" => $item["size"],
+ "Owner" => $item["owner"],
+ "Created at" => $item["created-at"],
+ "Filename" => $item["name"],
+ "Type" => $item["type"],
+ ];
+ }
+ foreach ($this->dirInfo as $k => $item) {
+ $rows[] = [
+ "Permission" => $item["perm"],
+ "Size" => $item["size"],
+ "Owner" => $item["owner"],
+ "Created at" => $item["created-at"],
+ "Filename" => $item["name"],
+ "Type" => $item["type"],
+ ];
+ }
+
+ $this->writeTableInOutputFormat($input, $output, $rows);
+ }
+}