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);
}
}