From bba30d8afff88268c3bc8a94c72c6cb8dc608e79 Mon Sep 17 00:00:00 2001 From: Git'Fellow <12234510+solracsf@users.noreply.github.com> Date: Thu, 2 Apr 2026 08:23:58 +0200 Subject: [PATCH] fix(quota): Invalidate root ETag on quota change Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com> --- lib/Folder/FolderManager.php | 14 ++++++++++++++ tests/Folder/FolderManagerTest.php | 31 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/lib/Folder/FolderManager.php b/lib/Folder/FolderManager.php index b7e94850d..98602d166 100644 --- a/lib/Folder/FolderManager.php +++ b/lib/Folder/FolderManager.php @@ -1057,6 +1057,20 @@ public function setFolderQuota(int $folderId, int $quota): void { $query->executeStatement(); $this->eventDispatcher->dispatchTyped(new CriticalActionPerformedEvent('The quota for groupfolder with id %d was set to %d bytes', [$folderId, $quota])); + + // Bump the root etag so desktop clients re-fetch quota-available-bytes on + // their next PROPFIND. Going through Cache::update() also emits the cache + // events notify_push relies on; no user session or mounted storage is needed. + try { + $folder = $this->getFolder($folderId); + if ($folder !== null) { + $storage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder->useSeparateStorage(), $folder); + $storage->getCache()->update($folder->rootId, ['etag' => uniqid()]); + } + } catch (\Exception $e) { + // Best effort: a failed invalidation must not fail the quota update + $this->logger->warning('Failed to invalidate group folder root etag after quota change', ['folderId' => $folderId, 'exception' => $e]); + } } /** diff --git a/tests/Folder/FolderManagerTest.php b/tests/Folder/FolderManagerTest.php index 8f553e47a..9d7b9b36f 100644 --- a/tests/Folder/FolderManagerTest.php +++ b/tests/Folder/FolderManagerTest.php @@ -493,6 +493,37 @@ public function testGetFolderPermissionsForUserMerge(): void { $this->assertEquals(0, $permissions); } + public function testSetFolderQuotaInvalidatesEtag(): void { + $this->config->expects($this->any()) + ->method('getSystemValueInt') + ->with('groupfolders.quota.default', FileInfo::SPACE_UNLIMITED) + ->willReturn(FileInfo::SPACE_UNLIMITED); + + $folderId = $this->manager->createFolder('quota-etag-test'); + + $folderBefore = $this->manager->getFolder($folderId); + $this->assertNotNull($folderBefore); + $etagBefore = $folderBefore->rootCacheEntry->getEtag(); + + $this->manager->setFolderQuota($folderId, 1024 * 1024 * 1024); + + $folderAfter = $this->manager->getFolder($folderId); + $this->assertNotNull($folderAfter); + $etagAfter = $folderAfter->rootCacheEntry->getEtag(); + + $this->assertNotEquals( + $etagBefore, + $etagAfter, + 'Etag must change after quota update so desktop clients re-fetch quota-available-bytes via PROPFIND', + ); + + $this->assertSame( + 1024 * 1024 * 1024, + $folderAfter->quota, + 'Quota value must be persisted correctly', + ); + } + public function testQuotaDefaultValue(): void { $folderId1 = $this->manager->createFolder('foo');