From efb1be91899a2e436b3300421d3c3bd73777ec64 Mon Sep 17 00:00:00 2001 From: Hoang Pham Date: Tue, 6 Jan 2026 17:32:24 +0700 Subject: [PATCH 1/8] fix: keep trashbin cache and db in sync Signed-off-by: Hoang Pham --- apps/files_trashbin/lib/Trashbin.php | 79 ++++++++++++++++++----- apps/files_trashbin/tests/StorageTest.php | 19 ++++++ 2 files changed, 81 insertions(+), 17 deletions(-) diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index ac3854c836e52..ab118301a4639 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -301,6 +301,8 @@ public static function move2trash($file_path, $ownerOnly = false) { $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath); if ($inCache) { $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath); + } else { + $trashStorage->getUpdater()->update($trashInternalPath); } } catch (CopyRecursiveException $e) { $moveSuccessful = false; @@ -329,7 +331,7 @@ public static function move2trash($file_path, $ownerOnly = false) { if ($moveSuccessful) { // there is still a possibility that the file has been deleted by a remote user $deletedBy = self::overwriteDeletedBy($user); - + $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); $query->insert('files_trash') ->setValue('id', $query->createNamedParameter($filename)) @@ -337,18 +339,51 @@ public static function move2trash($file_path, $ownerOnly = false) { ->setValue('location', $query->createNamedParameter($location)) ->setValue('user', $query->createNamedParameter($owner)) ->setValue('deleted_by', $query->createNamedParameter($deletedBy)); - $result = $query->executeStatement(); - if (!$result) { - \OC::$server->get(LoggerInterface::class)->error('trash bin database couldn\'t be updated', ['app' => 'files_trashbin']); + $inserted = false; + try { + $inserted = ($query->executeStatement() === 1); + } catch (\Throwable $e) { + \OC::$server->get(LoggerInterface::class)->error( + 'trash bin database insert failed', + [ + 'app' => 'files_trashbin', + 'exception' => $e, + 'user' => $owner, + 'filename' => $filename, + 'timestamp' => $timestamp, + ] + ); + } + if (!$inserted) { + Server::get(LoggerInterface::class)->error( + 'trash bin database couldn\'t be updated, removing trash payload', + [ + 'app' => 'files_trashbin', + 'user' => $owner, + 'filename' => $filename, + 'timestamp' => $timestamp, + ] + ); + if ($trashStorage->file_exists($trashInternalPath)) { + if ($trashStorage->is_dir($trashInternalPath)) { + $trashStorage->rmdir($trashInternalPath); + } else { + $trashStorage->unlink($trashInternalPath); + } + } + $trashStorage->getUpdater()->remove($trashInternalPath); + $moveSuccessful = false; } - Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path), - 'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]); + if ($inserted) { + Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path), + 'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]); - self::retainVersions($filename, $owner, $ownerPath, $timestamp); + self::retainVersions($filename, $owner, $ownerPath, $timestamp); - // if owner !== user we need to also add a copy to the users trash - if ($user !== $owner && $ownerOnly === false) { - self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp); + // if owner !== user we need to also add a copy to the users trash + if ($user !== $owner && $ownerOnly === false) { + self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp); + } } } @@ -672,13 +707,6 @@ public static function delete($filename, $user, $timestamp = null) { $size = 0; if ($timestamp) { - $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); - $query->delete('files_trash') - ->where($query->expr()->eq('user', $query->createNamedParameter($user))) - ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) - ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); - $query->executeStatement(); - $file = static::getTrashFilename($filename, $timestamp); } else { $file = $filename; @@ -689,6 +717,14 @@ public static function delete($filename, $user, $timestamp = null) { try { $node = $userRoot->get('/files_trashbin/files/' . $file); } catch (NotFoundException $e) { + if ($timestamp) { + $query = Server::get(IDBConnection::class)->getQueryBuilder(); + $query->delete('files_trash') + ->where($query->expr()->eq('user', $query->createNamedParameter($user))) + ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) + ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); + $query->executeStatement(); + } return $size; } @@ -702,6 +738,15 @@ public static function delete($filename, $user, $timestamp = null) { $node->delete(); self::emitTrashbinPostDelete('/files_trashbin/files/' . $file); + if ($timestamp) { + $query = Server::get(IDBConnection::class)->getQueryBuilder(); + $query->delete('files_trash') + ->where($query->expr()->eq('user', $query->createNamedParameter($user))) + ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) + ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); + $query->executeStatement(); + } + return $size; } diff --git a/apps/files_trashbin/tests/StorageTest.php b/apps/files_trashbin/tests/StorageTest.php index ec01e16b16ab5..c89314ba6359b 100644 --- a/apps/files_trashbin/tests/StorageTest.php +++ b/apps/files_trashbin/tests/StorageTest.php @@ -125,6 +125,25 @@ public function testSingleStorageDeleteFile(): void { $this->assertEquals('test.txt', substr($name, 0, strrpos($name, '.'))); } + public function testTrashEntryCreatedWhenSourceNotInCache(): void { + $this->userView->file_put_contents('uncached.txt', 'foo'); + + [$storage, $internalPath] = $this->userView->resolvePath('uncached.txt'); + $cache = $storage->getCache(); + $cache->remove($internalPath); + $this->assertFalse($cache->inCache($internalPath)); + + $this->userView->unlink('uncached.txt'); + + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); + $this->assertCount(1, $results); + $name = $results[0]->getName(); + $this->assertEquals('uncached.txt', substr($name, 0, strrpos($name, '.'))); + + [$trashStorage, $trashInternalPath] = $this->rootView->resolvePath('/' . $this->user . '/files_trashbin/files/' . $name); + $this->assertTrue($trashStorage->getCache()->inCache($trashInternalPath)); + } + /** * Test that deleting a folder puts it into the trashbin. */ From dc90d59e4f3dfd176dd3b74f445431f10fb794e6 Mon Sep 17 00:00:00 2001 From: Hoang Pham Date: Tue, 6 Jan 2026 18:40:21 +0700 Subject: [PATCH 2/8] fix(trashbin): keep metadata consistent on move Signed-off-by: Hoang Pham --- apps/files_trashbin/lib/Trashbin.php | 130 ++++++++++++++++----------- 1 file changed, 76 insertions(+), 54 deletions(-) diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index ab118301a4639..90c1750c26a9e 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -291,12 +291,60 @@ public static function move2trash($file_path, $ownerOnly = false) { $configuredTrashbinSize = static::getConfiguredTrashbinSize($owner); if ($configuredTrashbinSize >= 0 && $sourceInfo->getSize() >= $configuredTrashbinSize) { + $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); return false; } + // there is still a possibility that the file has been deleted by a remote user + $deletedBy = self::overwriteDeletedBy($user); + + $deleteTrashRow = static function () use ($owner, $filename, $timestamp): void { + $query = Server::get(IDBConnection::class)->getQueryBuilder(); + $query->delete('files_trash') + ->where($query->expr()->eq('user', $query->createNamedParameter($owner))) + ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) + ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); + $query->executeStatement(); + }; + + $query = Server::get(IDBConnection::class)->getQueryBuilder(); + $query->insert('files_trash') + ->setValue('id', $query->createNamedParameter($filename)) + ->setValue('timestamp', $query->createNamedParameter($timestamp)) + ->setValue('location', $query->createNamedParameter($location)) + ->setValue('user', $query->createNamedParameter($owner)) + ->setValue('deleted_by', $query->createNamedParameter($deletedBy)); + $inserted = false; try { - $moveSuccessful = true; + $inserted = ($query->executeStatement() === 1); + } catch (\Throwable $e) { + Server::get(LoggerInterface::class)->error( + 'trash bin database insert failed', + [ + 'app' => 'files_trashbin', + 'exception' => $e, + 'user' => $owner, + 'filename' => $filename, + 'timestamp' => $timestamp, + ] + ); + } + if (!$inserted) { + Server::get(LoggerInterface::class)->error( + 'trash bin database couldn\'t be updated, skipping trash move', + [ + 'app' => 'files_trashbin', + 'user' => $owner, + 'filename' => $filename, + 'timestamp' => $timestamp, + ] + ); + $trashStorage->releaseLock($trashInternalPath, ILockingProvider::LOCK_EXCLUSIVE, $lockingProvider); + return false; + } + $moveSuccessful = true; + try { $inCache = $sourceStorage->getCache()->inCache($sourceInternalPath); $trashStorage->moveFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath); if ($inCache) { @@ -325,65 +373,39 @@ public static function move2trash($file_path, $ownerOnly = false) { } else { $trashStorage->getUpdater()->remove($trashInternalPath); } - return false; + $moveSuccessful = false; } - if ($moveSuccessful) { - // there is still a possibility that the file has been deleted by a remote user - $deletedBy = self::overwriteDeletedBy($user); - - $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); - $query->insert('files_trash') - ->setValue('id', $query->createNamedParameter($filename)) - ->setValue('timestamp', $query->createNamedParameter($timestamp)) - ->setValue('location', $query->createNamedParameter($location)) - ->setValue('user', $query->createNamedParameter($owner)) - ->setValue('deleted_by', $query->createNamedParameter($deletedBy)); - $inserted = false; - try { - $inserted = ($query->executeStatement() === 1); - } catch (\Throwable $e) { - \OC::$server->get(LoggerInterface::class)->error( - 'trash bin database insert failed', - [ - 'app' => 'files_trashbin', - 'exception' => $e, - 'user' => $owner, - 'filename' => $filename, - 'timestamp' => $timestamp, - ] - ); - } - if (!$inserted) { - Server::get(LoggerInterface::class)->error( - 'trash bin database couldn\'t be updated, removing trash payload', - [ - 'app' => 'files_trashbin', - 'user' => $owner, - 'filename' => $filename, - 'timestamp' => $timestamp, - ] - ); - if ($trashStorage->file_exists($trashInternalPath)) { - if ($trashStorage->is_dir($trashInternalPath)) { - $trashStorage->rmdir($trashInternalPath); - } else { - $trashStorage->unlink($trashInternalPath); - } + if (!$moveSuccessful) { + Server::get(LoggerInterface::class)->error( + 'trash move failed, removing trash metadata and payload', + [ + 'app' => 'files_trashbin', + 'user' => $owner, + 'filename' => $filename, + 'timestamp' => $timestamp, + ] + ); + $deleteTrashRow(); + if ($trashStorage->file_exists($trashInternalPath)) { + if ($trashStorage->is_dir($trashInternalPath)) { + $trashStorage->rmdir($trashInternalPath); + } else { + $trashStorage->unlink($trashInternalPath); } - $trashStorage->getUpdater()->remove($trashInternalPath); - $moveSuccessful = false; } - if ($inserted) { - Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path), - 'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]); + $trashStorage->getUpdater()->remove($trashInternalPath); + } + + if ($moveSuccessful) { + Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', ['filePath' => Filesystem::normalizePath($file_path), + 'trashPath' => Filesystem::normalizePath(static::getTrashFilename($filename, $timestamp))]); - self::retainVersions($filename, $owner, $ownerPath, $timestamp); + self::retainVersions($filename, $owner, $ownerPath, $timestamp); - // if owner !== user we need to also add a copy to the users trash - if ($user !== $owner && $ownerOnly === false) { - self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp); - } + // if owner !== user we need to also add a copy to the users trash + if ($user !== $owner && $ownerOnly === false) { + self::copyFilesToUser($ownerPath, $owner, $file_path, $user, $timestamp); } } From e7641f7e0638a3d1bfc9dd41a02e16b61a88ffe4 Mon Sep 17 00:00:00 2001 From: Hoang Pham Date: Tue, 6 Jan 2026 18:40:41 +0700 Subject: [PATCH 3/8] perf(trashbin): avoid full rescan for uncached moves Signed-off-by: Hoang Pham --- apps/files_trashbin/lib/Trashbin.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index 90c1750c26a9e..8ac9ec128ff23 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -350,7 +350,11 @@ public static function move2trash($file_path, $ownerOnly = false) { if ($inCache) { $trashStorage->getUpdater()->renameFromStorage($sourceStorage, $sourceInternalPath, $trashInternalPath); } else { - $trashStorage->getUpdater()->update($trashInternalPath); + $sizeDifference = $sourceInfo->getSize(); + if ($sizeDifference < 0) { + $sizeDifference = null; + } + $trashStorage->getUpdater()->update($trashInternalPath, null, $sizeDifference); } } catch (CopyRecursiveException $e) { $moveSuccessful = false; From 92c4a22f0925ef55c097e1f2e9a3e86a94c15847 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 1 Apr 2026 18:09:29 +0200 Subject: [PATCH 4/8] chore: deduplicate trashbin row delete logic Signed-off-by: Robin Appelman --- apps/files_trashbin/lib/Trashbin.php | 41 +++++++++------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index 8ac9ec128ff23..ef19cee6804e8 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -298,15 +298,6 @@ public static function move2trash($file_path, $ownerOnly = false) { // there is still a possibility that the file has been deleted by a remote user $deletedBy = self::overwriteDeletedBy($user); - $deleteTrashRow = static function () use ($owner, $filename, $timestamp): void { - $query = Server::get(IDBConnection::class)->getQueryBuilder(); - $query->delete('files_trash') - ->where($query->expr()->eq('user', $query->createNamedParameter($owner))) - ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) - ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); - $query->executeStatement(); - }; - $query = Server::get(IDBConnection::class)->getQueryBuilder(); $query->insert('files_trash') ->setValue('id', $query->createNamedParameter($filename)) @@ -390,7 +381,7 @@ public static function move2trash($file_path, $ownerOnly = false) { 'timestamp' => $timestamp, ] ); - $deleteTrashRow(); + self::deleteTrashRow($user, $filename, $timestamp); if ($trashStorage->file_exists($trashInternalPath)) { if ($trashStorage->is_dir($trashInternalPath)) { $trashStorage->rmdir($trashInternalPath); @@ -589,12 +580,7 @@ public static function restore($file, $filename, $timestamp) { self::restoreVersions($view, $file, $filename, $uniqueFilename, $location, $timestamp); if ($timestamp) { - $query = \OC::$server->getDatabaseConnection()->getQueryBuilder(); - $query->delete('files_trash') - ->where($query->expr()->eq('user', $query->createNamedParameter($user))) - ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) - ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); - $query->executeStatement(); + self::deleteTrashRow($user, $filename, $timestamp); } return true; @@ -744,12 +730,7 @@ public static function delete($filename, $user, $timestamp = null) { $node = $userRoot->get('/files_trashbin/files/' . $file); } catch (NotFoundException $e) { if ($timestamp) { - $query = Server::get(IDBConnection::class)->getQueryBuilder(); - $query->delete('files_trash') - ->where($query->expr()->eq('user', $query->createNamedParameter($user))) - ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) - ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); - $query->executeStatement(); + self::deleteTrashRow($user, $filename, $timestamp); } return $size; } @@ -765,17 +746,21 @@ public static function delete($filename, $user, $timestamp = null) { self::emitTrashbinPostDelete('/files_trashbin/files/' . $file); if ($timestamp) { - $query = Server::get(IDBConnection::class)->getQueryBuilder(); - $query->delete('files_trash') - ->where($query->expr()->eq('user', $query->createNamedParameter($user))) - ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) - ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); - $query->executeStatement(); + self::deleteTrashRow($user, $filename, $timestamp); } return $size; } + private static function deleteTrashRow(string $user, string $filename, int $timestamp): void { + $query = Server::get(IDBConnection::class)->getQueryBuilder(); + $query->delete('files_trash') + ->where($query->expr()->eq('user', $query->createNamedParameter($user))) + ->andWhere($query->expr()->eq('id', $query->createNamedParameter($filename))) + ->andWhere($query->expr()->eq('timestamp', $query->createNamedParameter($timestamp))); + $query->executeStatement(); + } + /** * @param string $file * @param string $filename From 3ff73d08ae33a967714969244d6c7fd93c12d92c Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 1 Apr 2026 18:55:00 +0200 Subject: [PATCH 5/8] test: add test for trashbin when cross-storage move fails Signed-off-by: Robin Appelman --- apps/files_trashbin/tests/StorageTest.php | 60 +++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/apps/files_trashbin/tests/StorageTest.php b/apps/files_trashbin/tests/StorageTest.php index c89314ba6359b..9f2b444f5f58a 100644 --- a/apps/files_trashbin/tests/StorageTest.php +++ b/apps/files_trashbin/tests/StorageTest.php @@ -4,16 +4,20 @@ * SPDX-FileCopyrightText: 2016 ownCloud, Inc. * SPDX-License-Identifier: AGPL-3.0-only */ + namespace OCA\Files_Trashbin\Tests; +use OC\Files\Cache\Updater; use OC\Files\Filesystem; use OC\Files\Storage\Common; +use OC\Files\Storage\Local; use OC\Files\Storage\Temporary; use OC\Files\View; use OCA\Files_Trashbin\AppInfo\Application; use OCA\Files_Trashbin\Events\MoveToTrashEvent; use OCA\Files_Trashbin\Storage; use OCA\Files_Trashbin\Trash\ITrashManager; +use OCA\Files_Trashbin\Trashbin; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Constants; @@ -144,6 +148,62 @@ public function testTrashEntryCreatedWhenSourceNotInCache(): void { $this->assertTrue($trashStorage->getCache()->inCache($trashInternalPath)); } + public function testTrashEntryNotCreatedWhenDeleteFailed(): void { + $storage2 = $this->getMockBuilder(Temporary::class) + ->setConstructorArgs([]) + ->onlyMethods(['unlink', 'instanceOfStorage']) + ->getMock(); + $storage2->method('unlink') + ->willReturn(false); + + // disable same-storage move optimization + $storage2->method('instanceOfStorage') + ->willReturnCallback(fn (string $class) => ($class !== Local::class) && (new Temporary([]))->instanceOfStorage($class)); + + + Filesystem::mount($storage2, [], $this->user . '/files/substorage'); + $this->userView->file_put_contents('substorage/test.txt', 'foo'); + + $this->assertFalse($this->userView->unlink('substorage/test.txt')); + + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); + $this->assertEmpty($results); + + $trashData = Trashbin::getExtraData($this->user); + $this->assertEmpty($trashData); + } + + public function testTrashEntryNotCreatedWhenCacheRowFailed(): void { + $trashStorage = $this->getMockBuilder(Temporary::class) + ->setConstructorArgs([]) + ->onlyMethods(['getUpdater']) + ->getMock(); + $updater = $this->getMockBuilder(Updater::class) + ->setConstructorArgs([$trashStorage]) + ->onlyMethods(['renameFromStorage']) + ->getMock(); + $trashStorage->method('getUpdater') + ->willReturn($updater); + $updater->method('renameFromStorage') + ->willThrowException(new \Exception()); + + Filesystem::mount($trashStorage, [], $this->user . '/files_trashbin'); + $this->userView->file_put_contents('test.txt', 'foo'); + + try { + $this->assertFalse($this->userView->unlink('test.txt')); + $this->fail(); + } catch (\Exception) { + // expected + } + + $results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/'); + $this->assertEmpty($results); + + $trashData = Trashbin::getExtraData($this->user); + $this->assertEmpty($trashData); + } + /** * Test that deleting a folder puts it into the trashbin. */ From c9ae7989620cb1f5dea5e018174085c2478cf649 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 1 Apr 2026 19:17:25 +0200 Subject: [PATCH 6/8] fix: catch all exceptions during trashbin cache move Signed-off-by: Robin Appelman --- apps/files_trashbin/lib/Trashbin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index ef19cee6804e8..99343dbe1659e 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -347,7 +347,7 @@ public static function move2trash($file_path, $ownerOnly = false) { } $trashStorage->getUpdater()->update($trashInternalPath, null, $sizeDifference); } - } catch (CopyRecursiveException $e) { + } catch (\Exception $e) { $moveSuccessful = false; if ($trashStorage->file_exists($trashInternalPath)) { $trashStorage->unlink($trashInternalPath); From 9c3416e89be3a2f368b66dd49370ad3ee6df942a Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 1 Apr 2026 19:25:27 +0200 Subject: [PATCH 7/8] chore: psalm fix Signed-off-by: Robin Appelman --- apps/files_trashbin/lib/Trashbin.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/files_trashbin/lib/Trashbin.php b/apps/files_trashbin/lib/Trashbin.php index 99343dbe1659e..f72756e7aea89 100644 --- a/apps/files_trashbin/lib/Trashbin.php +++ b/apps/files_trashbin/lib/Trashbin.php @@ -344,6 +344,8 @@ public static function move2trash($file_path, $ownerOnly = false) { $sizeDifference = $sourceInfo->getSize(); if ($sizeDifference < 0) { $sizeDifference = null; + } else { + $sizeDifference = (int)$sizeDifference; } $trashStorage->getUpdater()->update($trashInternalPath, null, $sizeDifference); } From 185f427bbde103e0a270f896225c88b5203f28d5 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Wed, 1 Apr 2026 20:23:42 +0200 Subject: [PATCH 8/8] test: skip testTrashEntryCreatedWhenSourceNotInCache on object store Signed-off-by: Robin Appelman --- apps/files_trashbin/tests/StorageTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/files_trashbin/tests/StorageTest.php b/apps/files_trashbin/tests/StorageTest.php index 9f2b444f5f58a..0930492e5cd61 100644 --- a/apps/files_trashbin/tests/StorageTest.php +++ b/apps/files_trashbin/tests/StorageTest.php @@ -9,6 +9,7 @@ use OC\Files\Cache\Updater; use OC\Files\Filesystem; +use OC\Files\ObjectStore\ObjectStoreStorage; use OC\Files\Storage\Common; use OC\Files\Storage\Local; use OC\Files\Storage\Temporary; @@ -133,6 +134,9 @@ public function testTrashEntryCreatedWhenSourceNotInCache(): void { $this->userView->file_put_contents('uncached.txt', 'foo'); [$storage, $internalPath] = $this->userView->resolvePath('uncached.txt'); + if ($storage->instanceOfStorage(ObjectStoreStorage::class)) { + $this->markTestSkipped('object store always has the file in cache'); + } $cache = $storage->getCache(); $cache->remove($internalPath); $this->assertFalse($cache->inCache($internalPath));