From e51be33793b0a0491f4d6dcfa7c16af35c51fff5 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 400f2d7e0acc70f879b83888dc5d06711c29964a 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 de5f677af044ecb73657836e28417578f1128bf4 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 9f7097bdc7d0721c585119f7cd76b9cdc407c39f 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')