diff --git a/lib/Trash/TrashBackend.php b/lib/Trash/TrashBackend.php index 5bae78243..43676776c 100644 --- a/lib/Trash/TrashBackend.php +++ b/lib/Trash/TrashBackend.php @@ -656,7 +656,7 @@ public function expire(Expiration $expiration): array { $sizeInTrash += $node->getSize(); } - $size = $folder->rootCacheEntry->getSize(); + $folderSize = $folder->rootCacheEntry->getSize(); foreach ($trashItems as $groupTrashItem) { $nodeName = $groupTrashItem['name'] . '.d' . $groupTrashItem['deleted_time']; @@ -666,7 +666,7 @@ public function expire(Expiration $expiration): array { $node = $nodes[$nodeName]; - if ($expiration->isExpired($groupTrashItem['deleted_time'], $folder->quota > 0 && $folder->quota < ($size + $sizeInTrash))) { + if ($expiration->isExpired($groupTrashItem['deleted_time'], $folder->quota > 0 && $folder->quota < ($folderSize + $sizeInTrash))) { $this->logger->debug('expiring ' . $node->getPath()); if (!$this->unlinkTrashNode($node)) { $this->logger->error('Failed to remove item from trashbin: ' . $node->getPath()); @@ -676,7 +676,7 @@ public function expire(Expiration $expiration): array { // only count up after checking if removal is possible $count += 1; $size += $node->getSize(); - $size -= $node->getSize(); + $sizeInTrash -= $node->getSize(); $node->getStorage()->getCache()->remove($node->getInternalPath()); $this->trashManager->removeItem($folderId, $groupTrashItem['name'], $groupTrashItem['deleted_time']); if (!is_null($groupTrashItem['file_id']) && !is_null($this->versionsBackend)) { diff --git a/tests/Trash/TrashBackendTest.php b/tests/Trash/TrashBackendTest.php index 4ab316b7b..522d6d7b7 100644 --- a/tests/Trash/TrashBackendTest.php +++ b/tests/Trash/TrashBackendTest.php @@ -485,4 +485,30 @@ public function testRemoveItemWithMissingTrashFile(): void { $this->logout(); } + + public function testExpireStopsOnceBackUnderQuota(): void { + $this->loginAsUser('manager'); + + foreach (['a.txt', 'b.txt', 'c.txt'] as $name) { + $file = $this->managerUserFolder->newFile("{$this->folderName}/$name", str_repeat('x', 100)); + $this->trashBackend->moveToTrash($file->getStorage(), $file->getInternalPath()); + } + + $this->assertCount(3, $this->trashManager->listTrashForFolders([$this->folderId])); + + // over quota by less than one trash item, so expiring a single item is enough + $folderSize = $this->folderManager->getAllFoldersWithSize()[$this->folderId]->rootCacheEntry->getSize(); + $this->folderManager->setFolderQuota($this->folderId, (int)$folderSize + 250); + + $expiration = $this->createMock(Expiration::class); + $expiration->method('isExpired')->willReturnCallback(fn (int $timestamp, bool $quotaExceeded): bool => $quotaExceeded); + + [$count, $size] = $this->trashBackend->expire($expiration); + + $this->assertSame(1, $count); + $this->assertSame(100, $size); + $this->assertCount(2, $this->trashManager->listTrashForFolders([$this->folderId])); + + $this->logout(); + } }