From a1eac32a374f7bd76a73d4ab1843976a26e6108b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6?= <14975046+skjnldsv@users.noreply.github.com> Date: Mon, 12 Oct 2026 20:05:57 +0200 Subject: [PATCH] fix: notify the original path of files moved to the trash bin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the trash bin enabled, deleting a file moves its cache entry within the same storage, which only emits a cache update for the path in the trash bin. That path is filtered out, so clients never got the id of the deleted file. Also push the path the node was deleted from. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: John Molakvoæ <14975046+skjnldsv@users.noreply.github.com> --- lib/AppInfo/Application.php | 2 ++ lib/Listener.php | 48 +++++++++++++++++++++++------------ tests/lib/ListenerTest.php | 50 +++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 16 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index ad8a22ca..68333f77 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -26,6 +26,7 @@ use OCP\Files\Cache\CacheEntryInsertedEvent; use OCP\Files\Cache\CacheEntryRemovedEvent; use OCP\Files\Cache\CacheEntryUpdatedEvent; +use OCP\Files\Events\Node\NodeDeletedEvent; use OCP\Group\Events\UserAddedEvent; use OCP\Group\Events\UserRemovedEvent; use OCP\Security\CSP\AddContentSecurityPolicyEvent; @@ -71,6 +72,7 @@ public function attachHooks( $eventDispatcher->addListener(CacheEntryInsertedEvent::class, [$listener, 'cacheListener']); $eventDispatcher->addListener(CacheEntryUpdatedEvent::class, [$listener, 'cacheListener']); $eventDispatcher->addListener(CacheEntryRemovedEvent::class, [$listener, 'cacheListener']); + $eventDispatcher->addListener(NodeDeletedEvent::class, [$listener, 'nodeDeletedListener']); $eventDispatcher->addListener(UserAddedEvent::class, [$listener, 'groupListener']); $eventDispatcher->addListener(UserRemovedEvent::class, [$listener, 'groupListener']); diff --git a/lib/Listener.php b/lib/Listener.php index 97b13eec..64f421ed 100644 --- a/lib/Listener.php +++ b/lib/Listener.php @@ -14,7 +14,9 @@ use OCP\Activity\IEvent; use OCP\EventDispatcher\Event; use OCP\Files\Cache\ICacheEvent; +use OCP\Files\Events\Node\NodeDeletedEvent; use OCP\Files\IHomeStorage; +use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorage; use OCP\Group\Events\UserAddedEvent; use OCP\Group\Events\UserRemovedEvent; @@ -35,22 +37,36 @@ public function __construct(IQueue $queue) { public function cacheListener(Event $event): void { if ($event instanceof ICacheEvent) { - $path = $event->getPath(); - - $storage = $event->getStorage(); - while ($storage->instanceOfStorage(Jail::class)) { - /** @var Jail $storage */ - $path = $storage->getUnjailedPath($path); - $storage = $storage->getUnjailedStorage(); - } - - if ($this->shouldNotifyPath($event->getStorage(), $path)) { - $this->queue->push('notify_storage_update', [ - 'storage' => $event->getStorageId(), - 'path' => $path, - 'file_id' => $event->getFileId(), - ]); - } + $this->pushStorageUpdate($event->getStorage(), $event->getStorageId(), $event->getPath(), $event->getFileId()); + } + } + + /** + * Moving a file to the trash bin only updates its cache entry to the path in the trash bin, + * which users don't see. Notify the path it was deleted from as well. + */ + public function nodeDeletedListener(NodeDeletedEvent $event): void { + $node = $event->getNode(); + try { + $storage = $node->getStorage(); + $this->pushStorageUpdate($storage, $storage->getCache()->getNumericStorageId(), $node->getInternalPath(), $node->getId()); + } catch (NotFoundException) { + } + } + + private function pushStorageUpdate(IStorage $storage, int $storageId, string $path, int $fileId): void { + while ($storage->instanceOfStorage(Jail::class)) { + /** @var Jail $storage */ + $path = $storage->getUnjailedPath($path); + $storage = $storage->getUnjailedStorage(); + } + + if ($this->shouldNotifyPath($storage, $path)) { + $this->queue->push('notify_storage_update', [ + 'storage' => $storageId, + 'path' => $path, + 'file_id' => $fileId, + ]); } } diff --git a/tests/lib/ListenerTest.php b/tests/lib/ListenerTest.php index 7fdf3ad3..9676b5b2 100644 --- a/tests/lib/ListenerTest.php +++ b/tests/lib/ListenerTest.php @@ -11,6 +11,12 @@ use OCA\NotifyPush\Listener; use OCA\NotifyPush\Queue\IQueue; use OCP\Files\Cache\CacheEntryInsertedEvent; +use OCP\Files\Cache\CacheEntryUpdatedEvent; +use OCP\Files\Cache\ICache; +use OCP\Files\Events\Node\NodeDeletedEvent; +use OCP\Files\File; +use OCP\Files\IHomeStorage; +use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorage; use OCP\Group\Events\UserAddedEvent; use OCP\Group\Events\UserRemovedEvent; @@ -56,6 +62,50 @@ public function testCacheEvents() { ], $events); } + private function getHomeStorage(): IStorage { + $cache = $this->createMock(ICache::class); + $cache->method('getNumericStorageId')->willReturn(1); + $storage = $this->createMock(IStorage::class); + $storage->method('getCache')->willReturn($cache); + $storage->method('instanceOfStorage')->willReturnCallback(static fn (string $class): bool => $class === IHomeStorage::class); + return $storage; + } + + public function testFileMovedToTheTrashBin() { + $events = []; + $listener = new Listener($this->getQueue($events)); + $storage = $this->getHomeStorage(); + + // The cache entry moves to the trash bin, users don't see that path + $listener->cacheListener(new CacheEntryUpdatedEvent($storage, 'files_trashbin/files/report.md.d1760000000', 12, 1)); + $this->assertEquals([], $events); + + $node = $this->createMock(File::class); + $node->method('getStorage')->willReturn($storage); + $node->method('getInternalPath')->willReturn('files/Documents/report.md'); + $node->method('getId')->willReturn(12); + $listener->nodeDeletedListener(new NodeDeletedEvent($node)); + + $this->assertEquals([ + 'notify_storage_update' => [ + ['storage' => 1, 'path' => 'files/Documents/report.md', 'file_id' => 12], + ], + ], $events); + } + + public function testDeletedNodeWithoutInfo() { + $events = []; + $listener = new Listener($this->getQueue($events)); + + $node = $this->createMock(File::class); + $node->method('getStorage')->willReturn($this->getHomeStorage()); + $node->method('getInternalPath')->willReturn('files/report.md'); + $node->method('getId')->willThrowException(new NotFoundException()); + $listener->nodeDeletedListener(new NodeDeletedEvent($node)); + + $this->assertEquals([], $events); + } + public function testGroupEvents() { $events = []; $queue = $this->getQueue($events);