Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/files/appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
<command>OCA\Files\Command\Delete</command>
<command>OCA\Files\Command\Copy</command>
<command>OCA\Files\Command\Move</command>
<command>OCA\Files\Command\Mkdir</command>
<command>OCA\Files\Command\Touch</command>
<command>OCA\Files\Command\SanitizeFilenames</command>
<command>OCA\Files\Command\Mount\Refresh</command>
<command>OCA\Files\Command\Mount\ListMounts</command>
Expand Down
2 changes: 2 additions & 0 deletions apps/files/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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\\Mkdir' => $baseDir . '/../lib/Command/Mkdir.php',
'OCA\\Files\\Command\\Mount\\ListMounts' => $baseDir . '/../lib/Command/Mount/ListMounts.php',
'OCA\\Files\\Command\\Mount\\Refresh' => $baseDir . '/../lib/Command/Mount/Refresh.php',
'OCA\\Files\\Command\\Move' => $baseDir . '/../lib/Command/Move.php',
Expand All @@ -49,6 +50,7 @@
'OCA\\Files\\Command\\SanitizeFilenames' => $baseDir . '/../lib/Command/SanitizeFilenames.php',
'OCA\\Files\\Command\\Scan' => $baseDir . '/../lib/Command/Scan.php',
'OCA\\Files\\Command\\ScanAppData' => $baseDir . '/../lib/Command/ScanAppData.php',
'OCA\\Files\\Command\\Touch' => $baseDir . '/../lib/Command/Touch.php',
'OCA\\Files\\Command\\TransferOwnership' => $baseDir . '/../lib/Command/TransferOwnership.php',
'OCA\\Files\\Command\\WindowsCompatibleFilenames' => $baseDir . '/../lib/Command/WindowsCompatibleFilenames.php',
'OCA\\Files\\ConfigLexicon' => $baseDir . '/../lib/ConfigLexicon.php',
Expand Down
2 changes: 2 additions & 0 deletions apps/files/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,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\\Mkdir' => __DIR__ . '/..' . '/../lib/Command/Mkdir.php',
'OCA\\Files\\Command\\Mount\\ListMounts' => __DIR__ . '/..' . '/../lib/Command/Mount/ListMounts.php',
'OCA\\Files\\Command\\Mount\\Refresh' => __DIR__ . '/..' . '/../lib/Command/Mount/Refresh.php',
'OCA\\Files\\Command\\Move' => __DIR__ . '/..' . '/../lib/Command/Move.php',
Expand All @@ -64,6 +65,7 @@ class ComposerStaticInitFiles
'OCA\\Files\\Command\\SanitizeFilenames' => __DIR__ . '/..' . '/../lib/Command/SanitizeFilenames.php',
'OCA\\Files\\Command\\Scan' => __DIR__ . '/..' . '/../lib/Command/Scan.php',
'OCA\\Files\\Command\\ScanAppData' => __DIR__ . '/..' . '/../lib/Command/ScanAppData.php',
'OCA\\Files\\Command\\Touch' => __DIR__ . '/..' . '/../lib/Command/Touch.php',
'OCA\\Files\\Command\\TransferOwnership' => __DIR__ . '/..' . '/../lib/Command/TransferOwnership.php',
'OCA\\Files\\Command\\WindowsCompatibleFilenames' => __DIR__ . '/..' . '/../lib/Command/WindowsCompatibleFilenames.php',
'OCA\\Files\\ConfigLexicon' => __DIR__ . '/..' . '/../lib/ConfigLexicon.php',
Expand Down
12 changes: 10 additions & 2 deletions apps/files/lib/Command/Delete.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

use OC\Core\Command\Info\FileUtils;
use OCA\Files_Sharing\SharedStorage;
use OCA\Files_Trashbin\Trash\ITrashManager;
use OCP\Files\Folder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
Expand All @@ -21,7 +22,8 @@

class Delete extends Command {
public function __construct(
private FileUtils $fileUtils,
private readonly FileUtils $fileUtils,
private readonly ?ITrashManager $trashManager = null,
) {
parent::__construct();
}
Expand All @@ -32,14 +34,16 @@ protected function configure(): void {
->setName('files:delete')
->setDescription('Delete a file or folder')
->addArgument('file', InputArgument::REQUIRED, 'File id or path')
->addOption('force', 'f', InputOption::VALUE_NONE, "Don't ask for configuration and don't output any warnings");
->addOption('force', 'f', InputOption::VALUE_NONE, "Don't ask for configuration and don't output any warnings")
->addOption('skip-trash', null, InputOption::VALUE_NONE, 'Bypass the trashbin when deleting the file or folder');
}

#[\Override]
public function execute(InputInterface $input, OutputInterface $output): int {
$fileInput = $input->getArgument('file');
$inputIsId = is_numeric($fileInput);
$force = $input->getOption('force');
$skipTrash = $input->getOption('skip-trash');
$node = $this->fileUtils->getNode($fileInput);

if (!$node) {
Expand Down Expand Up @@ -90,6 +94,10 @@ public function execute(InputInterface $input, OutputInterface $output): int {

if ($deleteConfirmed) {
if ($node->isDeletable()) {
if ($skipTrash && $this->trashManager) {
$this->trashManager->pauseTrash();
}

$node->delete();
} else {
$output->writeln('<error>File cannot be deleted, insufficient permissions.</error>');
Expand Down
54 changes: 54 additions & 0 deletions apps/files/lib/Command/Mkdir.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Files\Command;

use OC\Core\Command\Info\FileUtils;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class Mkdir extends Command {
public function __construct(
private readonly FileUtils $fileUtils,
private readonly IRootFolder $rootFolder,
) {
parent::__construct();
}

#[\Override]
protected function configure(): void {
$this
->setName('files:mkdir')
->setDescription('Create a new directory')
->addArgument('path', InputArgument::REQUIRED, 'Target Nextcloud path for the new folder');
}

#[\Override]
public function execute(InputInterface $input, OutputInterface $output): int {
$path = $input->getArgument('path');
$node = $this->fileUtils->getNode($path);

if ($node instanceof Folder) {
$output->writeln("<info>$path already exists</info>");
return self::SUCCESS;
}
if ($node instanceof File) {
$output->writeln("<error>$path is a file</error>");
return self::FAILURE;
}

$this->rootFolder->newFolder($path);

return self::SUCCESS;
}
}
5 changes: 5 additions & 0 deletions apps/files/lib/Command/Put.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ public function execute(InputInterface $input, OutputInterface $output): int {
}
stream_copy_to_stream($source, $target);
} else {
$parentPath = dirname($fileOutput);
if (!$this->rootFolder->nodeExists($parentPath)) {
$this->rootFolder->newFolder($parentPath);
}

$this->rootFolder->newFile($fileOutput, $source);
}
return self::SUCCESS;
Expand Down
93 changes: 93 additions & 0 deletions apps/files/lib/Command/Touch.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Files\Command;

use DateTimeImmutable;
use OC\Core\Command\Info\FileUtils;
use OCP\Files\IRootFolder;
use Psr\Clock\ClockInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class Touch extends Command {
public function __construct(
private readonly FileUtils $fileUtils,
private readonly IRootFolder $rootFolder,
private readonly ClockInterface $clock,
) {
parent::__construct();
}

#[\Override]
protected function configure(): void {
$this
->setName('files:touch')
->setDescription('Update the last modified date of a file or folder, or create an empty file')
->addArgument('file', InputArgument::REQUIRED, 'Nextcloud path or fileid for the file or folder to change the modified date of')
->addOption('date', 'd', InputOption::VALUE_REQUIRED, 'Time to use as modified date instead of the current time. Acceptable formats are: ISO8601, "YYYY-MM-DD" and Unix time in seconds.')
->addOption('no-create', 'c', InputOption::VALUE_NONE, 'Don\'t create an empty file if the target path doesn\'t exist');
}

#[\Override]
public function execute(InputInterface $input, OutputInterface $output): int {
$fileInput = $input->getArgument('file');
$node = $this->fileUtils->getNode($fileInput);
$date = $input->getOption('date');
$noCreate = $input->getOption('no-create');

if (!$node) {
if ($noCreate || is_numeric($fileInput)) {
$output->writeln("<error>$fileInput doesn't exist</error>");
return self::FAILURE;
}
$node = $this->rootFolder->newFile($fileInput);
}

if ($date) {
$mtime = $this->parseDateOption($date);
if (!$mtime) {
$output->writeln("<error>Invalid date format '$date'. Acceptable formats are: ISO8601, \"YYYY-MM-DD\" and Unix time in seconds.</error>");
}
} else {
$mtime = $this->clock->now();
}
$node->touch($mtime->getTimestamp());

return self::SUCCESS;
}

/**
* @return \DateTimeImmutable|false
*/
protected function parseDateOption(string $input) {
$date = false;

// Handle Unix timestamp
if (filter_var($input, FILTER_VALIDATE_INT)) {
return new DateTimeImmutable('@' . $input);
}

// ISO8601
$date = DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, $input);
if ($date) {
return $date;
}
// With fractions
$date = DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uP', $input);
if ($date) {
return $date;
}

// YYYY-MM-DD
return DateTimeImmutable::createFromFormat('!Y-m-d', $input);
}
}
Loading