diff --git a/apps/files_versions/lib/Command/CleanUp.php b/apps/files_versions/lib/Command/CleanUp.php index d7bb4caa483ff..8ac0c2c03c21c 100644 --- a/apps/files_versions/lib/Command/CleanUp.php +++ b/apps/files_versions/lib/Command/CleanUp.php @@ -24,6 +24,7 @@ */ namespace OCA\Files_Versions\Command; +use OCA\Files_Versions\Db\VersionsMapper; use OCP\Files\IRootFolder; use OCP\IUserBackend; use OCP\IUserManager; @@ -45,7 +46,11 @@ class CleanUp extends Command { * @param IRootFolder $rootFolder * @param IUserManager $userManager */ - public function __construct(IRootFolder $rootFolder, IUserManager $userManager) { + public function __construct( + IRootFolder $rootFolder, + IUserManager $userManager, + protected VersionsMapper $versionMapper, + ) { parent::__construct(); $this->userManager = $userManager; $this->rootFolder = $rootFolder; @@ -130,6 +135,9 @@ protected function deleteVersions(string $user, string $path = null): void { \OC_Util::tearDownFS(); \OC_Util::setupFS($user); + $userHomeStorageId = $this->rootFolder->getUserFolder($user)->getStorage()->getCache()->getNumericStorageId(); + $this->versionMapper->deleteAllVersionsForUser($userHomeStorageId, $path); + $fullPath = '/' . $user . '/files_versions' . ($path ? '/' . $path : ''); if ($this->rootFolder->nodeExists($fullPath)) { $this->rootFolder->get($fullPath)->delete(); diff --git a/apps/files_versions/lib/Db/VersionsMapper.php b/apps/files_versions/lib/Db/VersionsMapper.php index 86a0be826681b..a8b8bc5f2b5c0 100644 --- a/apps/files_versions/lib/Db/VersionsMapper.php +++ b/apps/files_versions/lib/Db/VersionsMapper.php @@ -28,6 +28,7 @@ use OCA\Files_Versions\Db\VersionEntity; use OCP\IDBConnection; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\AppFramework\Db\QBMapper; use OCP\DB\IResult; @@ -85,4 +86,38 @@ public function deleteAllVersionsForFileId(int $fileId): int { ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId))) ->executeStatement(); } + + public function deleteAllVersionsForUser(int $storageId, string $path = null): void { + $fileIdsGenerator = $this->getFileIdsGenerator($storageId, $path); + + $versionEntitiesDeleteQuery = $this->db->getQueryBuilder(); + $versionEntitiesDeleteQuery->delete($this->getTableName()) + ->where($versionEntitiesDeleteQuery->expr()->in('file_id', $versionEntitiesDeleteQuery->createParameter('file_ids'))); + + foreach ($fileIdsGenerator as $fileIds) { + $versionEntitiesDeleteQuery->setParameter('file_ids', $fileIds, IQueryBuilder::PARAM_INT_ARRAY); + $versionEntitiesDeleteQuery->executeStatement(); + } + } + + private function getFileIdsGenerator(int $storageId, ?string $path): \Generator { + $offset = 0; + do { + $filesIdsSelect = $this->db->getQueryBuilder(); + $filesIdsSelect->select('fileid') + ->from('filecache') + ->where($filesIdsSelect->expr()->eq('storage', $filesIdsSelect->createNamedParameter($storageId, IQueryBuilder::PARAM_STR))) + ->andWhere($filesIdsSelect->expr()->like('path', $filesIdsSelect->createNamedParameter('files' . ($path ? '/' . $this->db->escapeLikeParameter($path) : '') . '/%', IQueryBuilder::PARAM_STR))) + ->andWhere($filesIdsSelect->expr()->gt('fileid', $filesIdsSelect->createParameter('offset'))) + ->setMaxResults(1000) + ->orderBy('fileid', 'ASC'); + + $filesIdsSelect->setParameter('offset', $offset, IQueryBuilder::PARAM_INT); + $result = $filesIdsSelect->executeQuery(); + $fileIds = $result->fetchAll(\PDO::FETCH_COLUMN); + $offset = end($fileIds); + + yield $fileIds; + } while (!empty($fileIds)); + } } diff --git a/apps/files_versions/tests/Command/CleanupTest.php b/apps/files_versions/tests/Command/CleanupTest.php index b5463aa61db14..085b25822f3dc 100644 --- a/apps/files_versions/tests/Command/CleanupTest.php +++ b/apps/files_versions/tests/Command/CleanupTest.php @@ -27,7 +27,10 @@ use OC\User\Manager; use OCA\Files_Versions\Command\CleanUp; +use OCP\Files\Cache\ICache; +use OCP\Files\Folder; use OCP\Files\IRootFolder; +use OCP\Files\Storage\IStorage; use Test\TestCase; /** @@ -48,6 +51,9 @@ class CleanupTest extends TestCase { /** @var \PHPUnit\Framework\MockObject\MockObject | IRootFolder */ protected $rootFolder; + /** @var \PHPUnit\Framework\MockObject\MockObject | VersionsMapper */ + protected $versionMapper; + protected function setUp(): void { parent::setUp(); @@ -55,9 +61,10 @@ protected function setUp(): void { ->disableOriginalConstructor()->getMock(); $this->userManager = $this->getMockBuilder('OC\User\Manager') ->disableOriginalConstructor()->getMock(); + $this->versionMapper = $this->getMockBuilder('OCA\Files_Versions\Db\VersionsMapper') + ->disableOriginalConstructor()->getMock(); - - $this->cleanup = new CleanUp($this->rootFolder, $this->userManager); + $this->cleanup = new CleanUp($this->rootFolder, $this->userManager, $this->versionMapper); } /** @@ -70,6 +77,21 @@ public function testDeleteVersions($nodeExists) { ->with('/testUser/files_versions') ->willReturn($nodeExists); + $userFolder = $this->createMock(Folder::class); + $userHomeStorage = $this->createMock(IStorage::class); + $userHomeStorageCache = $this->createMock(ICache::class); + $this->rootFolder->expects($this->once()) + ->method('getUserFolder') + ->willReturn($userFolder); + $userFolder->expects($this->once()) + ->method('getStorage') + ->willReturn($userHomeStorage); + $userHomeStorage->expects($this->once()) + ->method('getCache') + ->willReturn($userHomeStorageCache); + $userHomeStorageCache->expects($this->once()) + ->method('getNumericStorageId') + ->willReturn(1); if ($nodeExists) { $this->rootFolder->expects($this->once()) @@ -104,7 +126,7 @@ public function testExecuteDeleteListOfUsers() { $instance = $this->getMockBuilder('OCA\Files_Versions\Command\CleanUp') ->setMethods(['deleteVersions']) - ->setConstructorArgs([$this->rootFolder, $this->userManager]) + ->setConstructorArgs([$this->rootFolder, $this->userManager, $this->versionMapper]) ->getMock(); $instance->expects($this->exactly(count($userIds))) ->method('deleteVersions') @@ -136,7 +158,7 @@ public function testExecuteAllUsers() { $instance = $this->getMockBuilder('OCA\Files_Versions\Command\CleanUp') ->setMethods(['deleteVersions']) - ->setConstructorArgs([$this->rootFolder, $this->userManager]) + ->setConstructorArgs([$this->rootFolder, $this->userManager, $this->versionMapper]) ->getMock(); $backend = $this->getMockBuilder(\OCP\UserInterface::class)