setName('files:sanitize-filenames')
->setDescription('Renames files to match naming constraints')
->addArgument(
'user_id',
InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
'will only rename files the given user(s) have access to'
)
->addOption(
'dry-run',
mode: InputOption::VALUE_NONE,
description: 'Do not actually rename any files but just check filenames.',
)
->addOption(
'char-replacement',
'c',
mode: InputOption::VALUE_REQUIRED,
description: 'Replacement for invalid character (by default space, underscore or dash is used)',
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$this->charReplacement = $input->getOption('char-replacement');
// check if replacement is needed
$c = $this->filenameValidator->getForbiddenCharacters();
if (count($c) > 0) {
try {
$this->filenameValidator->sanitizeFilename($c[0], $this->charReplacement);
} catch (\InvalidArgumentException) {
if ($this->charReplacement === null) {
$output->writeln('Character replacement required');
} else {
$output->writeln('Invalid character replacement given');
}
return 1;
}
}
$this->dryRun = $input->getOption('dry-run');
if ($this->dryRun) {
$output->writeln('Dry run is enabled, no actual renaming will be applied.>');
}
$this->output = $output;
$users = $input->getArgument('user_id');
if (!empty($users)) {
foreach ($users as $userId) {
$user = $this->userManager->get($userId);
if ($user === null) {
$output->writeln("User '$userId' does not exist - skipping>");
continue;
}
$this->sanitizeUserFiles($user);
}
} else {
$this->userManager->callForSeenUsers($this->sanitizeUserFiles(...));
}
return self::SUCCESS;
}
private function sanitizeUserFiles(IUser $user): void {
// Set an active user so that event listeners can correctly work (e.g. files versions)
$this->session->setVolatileActiveUser($user);
$this->output->writeln('Analyzing files of ' . $user->getUID() . '>');
$folder = $this->rootFolder->getUserFolder($user->getUID());
$this->sanitizeFiles($folder);
}
private function sanitizeFiles(Folder $folder): void {
foreach ($folder->getDirectoryListing() as $node) {
$this->output->writeln('scanning: ' . $node->getPath(), OutputInterface::VERBOSITY_VERBOSE);
try {
$oldName = $node->getName();
$newName = $this->filenameValidator->sanitizeFilename($oldName, $this->charReplacement);
if ($oldName !== $newName) {
$newName = $folder->getNonExistingName($newName);
$path = rtrim(dirname($node->getPath()), '/');
if (!$this->dryRun) {
$node->move("$path/$newName");
} elseif (!$folder->isCreatable()) {
// simulate error for dry run
throw new NotPermittedException();
}
$this->output->writeln('renamed: "' . $oldName . '" to "' . $newName . '"');
}
} catch (LockedException) {
$this->output->writeln('skipping: ' . $node->getPath() . ' (file is locked)>');
} catch (NotPermittedException) {
$this->output->writeln('skipping: ' . $node->getPath() . ' (no permissions)>');
} catch (Exception) {
$this->output->writeln('failed: ' . $node->getPath() . '>');
}
if ($node instanceof Folder) {
$this->sanitizeFiles($node);
}
}
}
}