diff --git a/apps/files/lib/Command/DeleteOrphanedFiles.php b/apps/files/lib/Command/DeleteOrphanedFiles.php index c8ce9729ef..9795448ff9 100644 --- a/apps/files/lib/Command/DeleteOrphanedFiles.php +++ b/apps/files/lib/Command/DeleteOrphanedFiles.php @@ -78,6 +78,39 @@ class DeleteOrphanedFiles extends Command { } $output->writeln("$deletedEntries orphaned file cache entries deleted"); + + $deletedMounts = $this->cleanupOrphanedMounts(); + $output->writeln("$deletedMounts orphaned mount entries deleted"); return 0; } + + private function cleanupOrphanedMounts() { + $deletedEntries = 0; + + $query = $this->connection->getQueryBuilder(); + $query->select('m.storage_id') + ->from('mounts', 'm') + ->where($query->expr()->isNull('s.numeric_id')) + ->leftJoin('m', 'storages', 's', $query->expr()->eq('m.storage_id', 's.numeric_id')) + ->groupBy('storage_id') + ->setMaxResults(self::CHUNK_SIZE); + + $deleteQuery = $this->connection->getQueryBuilder(); + $deleteQuery->delete('mounts') + ->where($deleteQuery->expr()->eq('storage_id', $deleteQuery->createParameter('storageid'))); + + $deletedInLastChunk = self::CHUNK_SIZE; + while ($deletedInLastChunk === self::CHUNK_SIZE) { + $deletedInLastChunk = 0; + $result = $query->execute(); + while ($row = $result->fetch()) { + $deletedInLastChunk++; + $deletedEntries += $deleteQuery->setParameter('storageid', (int) $row['storage_id']) + ->execute(); + } + $result->closeCursor(); + } + + return $deletedEntries; + } } diff --git a/apps/files/tests/Command/DeleteOrphanedFilesTest.php b/apps/files/tests/Command/DeleteOrphanedFilesTest.php index 7147d58891..556c8d5eee 100644 --- a/apps/files/tests/Command/DeleteOrphanedFilesTest.php +++ b/apps/files/tests/Command/DeleteOrphanedFilesTest.php @@ -87,6 +87,11 @@ class DeleteOrphanedFilesTest extends TestCase { return $stmt->fetchAll(); } + protected function getMounts($storageId) { + $stmt = $this->connection->executeQuery('SELECT * FROM `*PREFIX*mounts` WHERE `storage_id` = ?', [$storageId]); + return $stmt->fetchAll(); + } + /** * Test clearing orphaned files */ @@ -98,20 +103,28 @@ class DeleteOrphanedFilesTest extends TestCase { ->disableOriginalConstructor() ->getMock(); + // scan home storage so that mounts are properly setup + \OC::$server->getRootFolder()->getUserFolder($this->user1)->getStorage()->getScanner()->scan(''); + $this->loginAsUser($this->user1); + $view = new View('/' . $this->user1 . '/'); $view->mkdir('files/test'); $fileInfo = $view->getFileInfo('files/test'); $storageId = $fileInfo->getStorage()->getId(); + $numericStorageId = $fileInfo->getStorage()->getStorageCache()->getNumericId(); $this->assertCount(1, $this->getFile($fileInfo->getId()), 'Asserts that file is available'); + $this->assertCount(1, $this->getMounts($numericStorageId), 'Asserts that mount is available'); $this->command->execute($input, $output); $this->assertCount(1, $this->getFile($fileInfo->getId()), 'Asserts that file is still available'); + $this->assertCount(1, $this->getMounts($numericStorageId), 'Asserts that mount is still available'); + $deletedRows = $this->connection->executeUpdate('DELETE FROM `*PREFIX*storages` WHERE `id` = ?', [$storageId]); $this->assertNotNull($deletedRows, 'Asserts that storage got deleted'); @@ -119,13 +132,19 @@ class DeleteOrphanedFilesTest extends TestCase { // parent folder, `files`, ´test` and `welcome.txt` => 4 elements $output - ->expects($this->once()) + ->expects($this->at(0)) ->method('writeln') ->with('3 orphaned file cache entries deleted'); + $output + ->expects($this->at(1)) + ->method('writeln') + ->with('1 orphaned mount entries deleted'); + $this->command->execute($input, $output); $this->assertCount(0, $this->getFile($fileInfo->getId()), 'Asserts that file gets cleaned up'); + $this->assertCount(0, $this->getMounts($numericStorageId), 'Asserts that mount gets cleaned up'); // since we deleted the storage it might throw a (valid) StorageNotAvailableException try {