diff --git a/apps/files/appinfo/info.xml b/apps/files/appinfo/info.xml
index c710df2fb1723..24ffe49a02f0b 100644
--- a/apps/files/appinfo/info.xml
+++ b/apps/files/appinfo/info.xml
@@ -46,6 +46,8 @@
OCA\Files\Command\Delete
OCA\Files\Command\Copy
OCA\Files\Command\Move
+ OCA\Files\Command\Mkdir
+ OCA\Files\Command\Touch
OCA\Files\Command\SanitizeFilenames
OCA\Files\Command\Mount\Refresh
OCA\Files\Command\Mount\ListMounts
diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php
index 6f4d17a78f13b..2e2cb6da8a601 100644
--- a/apps/files/composer/composer/autoload_classmap.php
+++ b/apps/files/composer/composer/autoload_classmap.php
@@ -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',
@@ -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',
diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php
index 9bcefe76c340f..d7786af11072c 100644
--- a/apps/files/composer/composer/autoload_static.php
+++ b/apps/files/composer/composer/autoload_static.php
@@ -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',
@@ -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',
diff --git a/apps/files/lib/Command/Delete.php b/apps/files/lib/Command/Delete.php
index 0558a6778b795..627b993bcce5d 100644
--- a/apps/files/lib/Command/Delete.php
+++ b/apps/files/lib/Command/Delete.php
@@ -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;
@@ -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();
}
@@ -32,7 +34,8 @@ 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]
@@ -40,6 +43,7 @@ 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) {
@@ -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('File cannot be deleted, insufficient permissions.');
diff --git a/apps/files/lib/Command/Mkdir.php b/apps/files/lib/Command/Mkdir.php
new file mode 100644
index 0000000000000..a7c47ccd362b7
--- /dev/null
+++ b/apps/files/lib/Command/Mkdir.php
@@ -0,0 +1,54 @@
+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("$path already exists");
+ return self::SUCCESS;
+ }
+ if ($node instanceof File) {
+ $output->writeln("$path is a file");
+ return self::FAILURE;
+ }
+
+ $this->rootFolder->newFolder($path);
+
+ return self::SUCCESS;
+ }
+}
diff --git a/apps/files/lib/Command/Put.php b/apps/files/lib/Command/Put.php
index 6de11c7a881d8..7907a3c6d2ee0 100644
--- a/apps/files/lib/Command/Put.php
+++ b/apps/files/lib/Command/Put.php
@@ -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;
diff --git a/apps/files/lib/Command/Touch.php b/apps/files/lib/Command/Touch.php
new file mode 100644
index 0000000000000..217ce721aa4b4
--- /dev/null
+++ b/apps/files/lib/Command/Touch.php
@@ -0,0 +1,93 @@
+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("$fileInput doesn't exist");
+ return self::FAILURE;
+ }
+ $node = $this->rootFolder->newFile($fileInput);
+ }
+
+ if ($date) {
+ $mtime = $this->parseDateOption($date);
+ if (!$mtime) {
+ $output->writeln("Invalid date format '$date'. Acceptable formats are: ISO8601, \"YYYY-MM-DD\" and Unix time in seconds.");
+ }
+ } 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);
+ }
+}