From ad6ca19471582711429026538f44c30fee64157d Mon Sep 17 00:00:00 2001 From: Julius Knorr Date: Fri, 29 May 2026 15:53:08 +0200 Subject: [PATCH 1/4] fix(occ): resolve info:file for files in the trashbin The info:file command scoped the file id lookup to the user files folder, so files in the trashbin could not be resolved. Fall back to searching the user root which also covers files_trashbin and files_versions. AI-assistant: Claude Code 2.1.156 (Claude Opus 4.8) Co-Authored-By: Claude Opus 4.8 Signed-off-by: Julius Knorr --- core/Command/Info/FileUtils.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/Command/Info/FileUtils.php b/core/Command/Info/FileUtils.php index 1c38a7609e444..405bf24ec4a02 100644 --- a/core/Command/Info/FileUtils.php +++ b/core/Command/Info/FileUtils.php @@ -79,7 +79,12 @@ public function getNode(string $fileInput): ?Node { } $mount = reset($mounts); $userFolder = $this->rootFolder->getUserFolder($mount->getUser()->getUID()); - return $userFolder->getFirstNodeById((int)$fileInput); + $node = $userFolder->getFirstNodeById((int)$fileInput); + if ($node) { + return $node; + } + // the file might live outside of the user files folder, e.g. in the trashbin or versions + return $userFolder->getParent()->getFirstNodeById((int)$fileInput); } else { try { return $this->rootFolder->get($fileInput); From ccbd8c615ced42fa73475dc51760a1947c4c19d4 Mon Sep 17 00:00:00 2001 From: Julius Knorr Date: Fri, 29 May 2026 15:59:27 +0200 Subject: [PATCH 2/4] fix(occ): resolve info:file for appdata and other rootless files Files that are not part of any user mount, such as appdata on the root storage, could not be resolved by id. Fall back to looking the file up directly on the root storage when no user mount matches. AI-assistant: Claude Code 2.1.156 (Claude Opus 4.8) Co-Authored-By: Claude Opus 4.8 Signed-off-by: Julius Knorr --- core/Command/Info/FileUtils.php | 44 ++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/core/Command/Info/FileUtils.php b/core/Command/Info/FileUtils.php index 405bf24ec4a02..1447cbbab212b 100644 --- a/core/Command/Info/FileUtils.php +++ b/core/Command/Info/FileUtils.php @@ -73,18 +73,23 @@ public function getFilesByUser(FileInfo $file): array { */ public function getNode(string $fileInput): ?Node { if (is_numeric($fileInput)) { - $mounts = $this->userMountCache->getMountsForFileId((int)$fileInput); - if (!$mounts) { - return null; - } - $mount = reset($mounts); - $userFolder = $this->rootFolder->getUserFolder($mount->getUser()->getUID()); - $node = $userFolder->getFirstNodeById((int)$fileInput); - if ($node) { - return $node; + $id = (int)$fileInput; + $mounts = $this->userMountCache->getMountsForFileId($id); + if ($mounts) { + $mount = reset($mounts); + $userFolder = $this->rootFolder->getUserFolder($mount->getUser()->getUID()); + $node = $userFolder->getFirstNodeById($id); + if ($node) { + return $node; + } + // the file might live outside of the user files folder, e.g. in the trashbin or versions + $node = $userFolder->getParent()->getFirstNodeById($id); + if ($node) { + return $node; + } } - // the file might live outside of the user files folder, e.g. in the trashbin or versions - return $userFolder->getParent()->getFirstNodeById((int)$fileInput); + // the file might not belong to a user at all, e.g. appdata on the root storage + return $this->getNodeFromRootMount($id); } else { try { return $this->rootFolder->get($fileInput); @@ -94,6 +99,23 @@ public function getNode(string $fileInput): ?Node { } } + /** + * Resolve a file id directly on the root storage, covering files that are + * not part of any user mount such as appdata. + */ + private function getNodeFromRootMount(int $id): ?Node { + $mount = $this->rootFolder->getMount(''); + $storage = $mount->getStorage(); + if ($storage === null) { + return null; + } + $cacheEntry = $storage->getCache()->get($id); + if ($cacheEntry === false) { + return null; + } + return $this->rootFolder->getNodeFromCacheEntryAndMount($cacheEntry, $mount); + } + public function formatPermissions(string $type, int $permissions): string { if ($permissions === Constants::PERMISSION_ALL || ($type === 'file' && $permissions === (Constants::PERMISSION_ALL - Constants::PERMISSION_CREATE))) { return 'full permissions'; From 6108af0d7f20f5ca4fe48d1056df48b66ea7b9a4 Mon Sep 17 00:00:00 2001 From: Julius Knorr Date: Fri, 29 May 2026 16:02:49 +0200 Subject: [PATCH 3/4] fix(sharing): indicate trashed files when listing orphan shares A shared file moved to the trashbin keeps its file id, so it is flagged as an orphan share while still existing in the filecache. Detect this case and tell the admin the file is in the share owner's trashbin instead of the generic lost-access message. AI-assistant: Claude Code 2.1.156 (Claude Opus 4.8) Co-Authored-By: Claude Opus 4.8 Signed-off-by: Julius Knorr --- apps/files_sharing/lib/Command/DeleteOrphanShares.php | 6 +++++- apps/files_sharing/lib/OrphanHelper.php | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/lib/Command/DeleteOrphanShares.php b/apps/files_sharing/lib/Command/DeleteOrphanShares.php index e15581b4113cf..71c0c137754d8 100644 --- a/apps/files_sharing/lib/Command/DeleteOrphanShares.php +++ b/apps/files_sharing/lib/Command/DeleteOrphanShares.php @@ -52,7 +52,11 @@ public function execute(InputInterface $input, OutputInterface $output): int { $exists = $this->orphanHelper->fileExists($share['fileid']); $output->writeln("{$share['target']} owned by {$share['owner']}"); if ($exists) { - $output->writeln(" file still exists but the share owner lost access to it, run occ info:file {$share['fileid']} for more information about the file"); + if ($this->orphanHelper->isInTrashbin($share['fileid'])) { + $output->writeln(" file is in the trashbin of the share owner, run occ info:file {$share['fileid']} for more information about the file"); + } else { + $output->writeln(" file still exists but the share owner lost access to it, run occ info:file {$share['fileid']} for more information about the file"); + } } else { $output->writeln(' file no longer exists'); } diff --git a/apps/files_sharing/lib/OrphanHelper.php b/apps/files_sharing/lib/OrphanHelper.php index 026cc48c56615..c20468dfcc64d 100644 --- a/apps/files_sharing/lib/OrphanHelper.php +++ b/apps/files_sharing/lib/OrphanHelper.php @@ -56,6 +56,15 @@ public function fileExists(int $fileId): bool { return $query->executeQuery()->fetchOne() !== false; } + public function isInTrashbin(int $fileId): bool { + $query = $this->connection->getQueryBuilder(); + $query->select('path') + ->from('filecache') + ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); + $path = $query->executeQuery()->fetchOne(); + return $path !== false && str_starts_with($path, 'files_trashbin/'); + } + /** * @return \Traversable */ From ca224da348b6ed710432953dc8d453dcce4ab327 Mon Sep 17 00:00:00 2001 From: Julius Knorr Date: Thu, 24 Sep 2026 15:13:16 +0200 Subject: [PATCH 4/4] docs(files_sharing): note group folders trashbin limitation isInTrashbin() only recognizes the regular user trashbin path layout, not the group folders trash storage. Documenting per review feedback on nextcloud/server#60838. Signed-off-by: Julius Knorr Assisted-by: ClaudeCode:claude-sonnet-5 Co-Authored-By: Claude Sonnet 5 --- apps/files_sharing/lib/OrphanHelper.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/files_sharing/lib/OrphanHelper.php b/apps/files_sharing/lib/OrphanHelper.php index c20468dfcc64d..ac2d8f18cd3a3 100644 --- a/apps/files_sharing/lib/OrphanHelper.php +++ b/apps/files_sharing/lib/OrphanHelper.php @@ -56,6 +56,9 @@ public function fileExists(int $fileId): bool { return $query->executeQuery()->fetchOne() !== false; } + /** + * Note: only detects the regular user trashbin path layout, not group folders trash + */ public function isInTrashbin(int $fileId): bool { $query = $this->connection->getQueryBuilder(); $query->select('path')