]> source.dussan.org Git - nextcloud-server.git/commitdiff
repair -1 folder sizes for object store background scan 37691/head
authorRobin Appelman <robin@icewind.nl>
Wed, 12 Apr 2023 16:08:14 +0000 (18:08 +0200)
committerRobin Appelman <robin@icewind.nl>
Wed, 10 May 2023 17:33:26 +0000 (19:33 +0200)
Signed-off-by: Robin Appelman <robin@icewind.nl>
apps/files_sharing/lib/Scanner.php
apps/files_sharing/tests/External/ScannerTest.php
lib/composer/composer/autoload_classmap.php
lib/composer/composer/autoload_static.php
lib/private/Files/Cache/Scanner.php
lib/private/Files/ObjectStore/NoopScanner.php [deleted file]
lib/private/Files/ObjectStore/ObjectStoreScanner.php [new file with mode: 0644]
lib/private/Files/ObjectStore/ObjectStoreStorage.php
tests/lib/Files/ObjectStore/NoopScannerTest.php [deleted file]
tests/lib/Files/ObjectStore/ObjectStoreScannerTest.php [new file with mode: 0644]

index baab7a862bd1ea5042acb8804170dc5845550561..d5a1c24418e260e3dd2f778e61a0bda23512e9ef 100644 (file)
@@ -25,7 +25,7 @@
 
 namespace OCA\Files_Sharing;
 
-use OC\Files\ObjectStore\NoopScanner;
+use OC\Files\ObjectStore\ObjectStoreScanner;
 
 /**
  * Scanner for SharedStorage
@@ -72,7 +72,8 @@ class Scanner extends \OC\Files\Cache\Scanner {
 
        public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
                $sourceScanner = $this->getSourceScanner();
-               if ($sourceScanner instanceof NoopScanner) {
+               if ($sourceScanner instanceof ObjectStoreScanner) {
+                       // ObjectStoreScanner doesn't scan
                        return [];
                } else {
                        return parent::scanFile($file, $reuseExisting, $parentId, $cacheData, $lock);
index 2d2486737dc1062a55bc47e2059d8a4d31925611..8d077715b2d980e730c05107e00de518e8ee25af 100644 (file)
@@ -26,9 +26,11 @@ namespace OCA\Files_Sharing\Tests\External;
 use OCA\Files_Sharing\External\Scanner;
 use Test\TestCase;
 
+/**
+ * @group DB
+ */
 class ScannerTest extends TestCase {
-       /** @var \OCA\Files_Sharing\External\Scanner */
-       protected $scanner;
+       protected Scanner $scanner;
        /** @var \OCA\Files_Sharing\External\Storage|\PHPUnit\Framework\MockObject\MockObject */
        protected $storage;
        /** @var \OC\Files\Cache\Cache|\PHPUnit\Framework\MockObject\MockObject */
index ec11cda456dec80d11059a77d36321f3927c69c1..479ebfb5222a89d40e627376597a31f43000e837 100644 (file)
@@ -1258,7 +1258,7 @@ return array(
     'OC\\Files\\ObjectStore\\Azure' => $baseDir . '/lib/private/Files/ObjectStore/Azure.php',
     'OC\\Files\\ObjectStore\\HomeObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php',
     'OC\\Files\\ObjectStore\\Mapper' => $baseDir . '/lib/private/Files/ObjectStore/Mapper.php',
-    'OC\\Files\\ObjectStore\\NoopScanner' => $baseDir . '/lib/private/Files/ObjectStore/NoopScanner.php',
+    'OC\\Files\\ObjectStore\\ObjectStoreScanner' => $baseDir . '/lib/private/Files/ObjectStore/ObjectStoreScanner.php',
     'OC\\Files\\ObjectStore\\ObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php',
     'OC\\Files\\ObjectStore\\S3' => $baseDir . '/lib/private/Files/ObjectStore/S3.php',
     'OC\\Files\\ObjectStore\\S3ConnectionTrait' => $baseDir . '/lib/private/Files/ObjectStore/S3ConnectionTrait.php',
index 308290b95bcdadb826e6ecc47ce2ecfbed31c7a0..390c1fb60e15d24daf2cf1ae3af6501e693b4dd5 100644 (file)
@@ -1291,7 +1291,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
         'OC\\Files\\ObjectStore\\Azure' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Azure.php',
         'OC\\Files\\ObjectStore\\HomeObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/HomeObjectStoreStorage.php',
         'OC\\Files\\ObjectStore\\Mapper' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Mapper.php',
-        'OC\\Files\\ObjectStore\\NoopScanner' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/NoopScanner.php',
+        'OC\\Files\\ObjectStore\\ObjectStoreScanner' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/ObjectStoreScanner.php',
         'OC\\Files\\ObjectStore\\ObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php',
         'OC\\Files\\ObjectStore\\S3' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3.php',
         'OC\\Files\\ObjectStore\\S3ConnectionTrait' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3ConnectionTrait.php',
index 6969828782a4a6fbbdfa15c75547e6806488f62f..e3a08264716db9d3c48f9e79eb6ade6b2cdbb3b6 100644 (file)
@@ -41,8 +41,8 @@ use OCP\Files\Cache\IScanner;
 use OCP\Files\ForbiddenException;
 use OCP\Files\NotFoundException;
 use OCP\Files\Storage\IReliableEtagStorage;
+use OCP\IDBConnection;
 use OCP\Lock\ILockingProvider;
-use OC\Files\Storage\Wrapper\Encoding;
 use OC\Files\Storage\Wrapper\Jail;
 use OC\Hooks\BasicEmitter;
 use Psr\Log\LoggerInterface;
@@ -89,12 +89,15 @@ class Scanner extends BasicEmitter implements IScanner {
         */
        protected $lockingProvider;
 
+       protected IDBConnection $connection;
+
        public function __construct(\OC\Files\Storage\Storage $storage) {
                $this->storage = $storage;
                $this->storageId = $this->storage->getId();
                $this->cache = $storage->getCache();
                $this->cacheActive = !\OC::$server->getConfig()->getSystemValueBool('filesystem_cache_readonly', false);
                $this->lockingProvider = \OC::$server->getLockingProvider();
+               $this->connection = \OC::$server->get(IDBConnection::class);
        }
 
        /**
@@ -430,7 +433,7 @@ class Scanner extends BasicEmitter implements IScanner {
                }
 
                if ($this->useTransactions) {
-                       \OC::$server->getDatabaseConnection()->beginTransaction();
+                       $this->connection->beginTransaction();
                }
 
                $exceptionOccurred = false;
@@ -473,8 +476,8 @@ class Scanner extends BasicEmitter implements IScanner {
                                // process is running in parallel
                                // log and ignore
                                if ($this->useTransactions) {
-                                       \OC::$server->getDatabaseConnection()->rollback();
-                                       \OC::$server->getDatabaseConnection()->beginTransaction();
+                                       $this->connection->rollback();
+                                       $this->connection->beginTransaction();
                                }
                                \OC::$server->get(LoggerInterface::class)->debug('Exception while scanning file "' . $child . '"', [
                                        'app' => 'core',
@@ -483,7 +486,7 @@ class Scanner extends BasicEmitter implements IScanner {
                                $exceptionOccurred = true;
                        } catch (\OCP\Lock\LockedException $e) {
                                if ($this->useTransactions) {
-                                       \OC::$server->getDatabaseConnection()->rollback();
+                                       $this->connection->rollback();
                                }
                                throw $e;
                        }
@@ -494,7 +497,7 @@ class Scanner extends BasicEmitter implements IScanner {
                        $this->removeFromCache($child);
                }
                if ($this->useTransactions) {
-                       \OC::$server->getDatabaseConnection()->commit();
+                       $this->connection->commit();
                }
                if ($exceptionOccurred) {
                        // It might happen that the parallel scan process has already
@@ -558,7 +561,7 @@ class Scanner extends BasicEmitter implements IScanner {
                }
        }
 
-       private function runBackgroundScanJob(callable $callback, $path) {
+       protected function runBackgroundScanJob(callable $callback, $path) {
                try {
                        $callback();
                        \OC_Hook::emit('Scanner', 'correctFolderSize', ['path' => $path]);
diff --git a/lib/private/Files/ObjectStore/NoopScanner.php b/lib/private/Files/ObjectStore/NoopScanner.php
deleted file mode 100644 (file)
index bdfc937..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-namespace OC\Files\ObjectStore;
-
-use OC\Files\Cache\Scanner;
-use OC\Files\Storage\Storage;
-
-class NoopScanner extends Scanner {
-       public function __construct(Storage $storage) {
-               // we don't need the storage, so do nothing here
-       }
-
-       /**
-        * scan a single file and store it in the cache
-        *
-        * @param string $file
-        * @param int $reuseExisting
-        * @param int $parentId
-        * @param array|null $cacheData existing data in the cache for the file to be scanned
-        * @return array an array of metadata of the scanned file
-        */
-       public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
-               return [];
-       }
-
-       /**
-        * scan a folder and all it's children
-        *
-        * @param string $path
-        * @param bool $recursive
-        * @param int $reuse
-        * @return array with the meta data of the scanned file or folder
-        */
-       public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
-               return [];
-       }
-
-       /**
-        * scan all the files and folders in a folder
-        *
-        * @param string $path
-        * @param bool $recursive
-        * @param int $reuse
-        * @param array $folderData existing cache data for the folder to be scanned
-        * @return int the size of the scanned folder or -1 if the size is unknown at this stage
-        */
-       protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true, array $data = []) {
-               return 0;
-       }
-
-       /**
-        * walk over any folders that are not fully scanned yet and scan them
-        */
-       public function backgroundScan() {
-               //noop
-       }
-}
diff --git a/lib/private/Files/ObjectStore/ObjectStoreScanner.php b/lib/private/Files/ObjectStore/ObjectStoreScanner.php
new file mode 100644 (file)
index 0000000..e589ca5
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+/**
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ *
+ * @author Christoph Wurst <christoph@winzerhof-wurst.at>
+ * @author Joas Schilling <coding@schilljs.com>
+ * @author Jörn Friedrich Dreyer <jfd@butonic.de>
+ * @author Morris Jobke <hey@morrisjobke.de>
+ * @author Robin Appelman <robin@icewind.nl>
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+namespace OC\Files\ObjectStore;
+
+use OC\Files\Cache\Scanner;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\Files\FileInfo;
+
+class ObjectStoreScanner extends Scanner {
+       public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) {
+               return [];
+       }
+
+       public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) {
+               return [];
+       }
+
+       protected function scanChildren($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $folderId = null, $lock = true, array $data = []) {
+               return 0;
+       }
+
+       public function backgroundScan() {
+               $lastPath = null;
+               // find any path marked as unscanned and run the scanner until no more paths are unscanned (or we get stuck)
+               // we sort by path DESC to ensure that contents of a folder are handled before the parent folder
+               while (($path = $this->getIncomplete()) !== false && $path !== $lastPath) {
+                       $this->runBackgroundScanJob(function () use ($path) {
+                               $item = $this->cache->get($path);
+                               if ($item && $item->getMimeType() !== FileInfo::MIMETYPE_FOLDER) {
+                                       $fh = $this->storage->fopen($path, 'r');
+                                       if ($fh) {
+                                               $stat = fstat($fh);
+                                               if ($stat['size']) {
+                                                       $this->cache->update($item->getId(), ['size' => $stat['size']]);
+                                               }
+                                       }
+                               }
+                       }, $path);
+                       // FIXME: this won't proceed with the next item, needs revamping of getIncomplete()
+                       // to make this possible
+                       $lastPath = $path;
+               }
+       }
+
+       /**
+        * Unlike the default Cache::getIncomplete this one sorts by path.
+        *
+        * This is needed since self::backgroundScan doesn't fix child entries when running on a parent folder.
+        * By sorting by path we ensure that we encounter the child entries first.
+        *
+        * @return false|string
+        * @throws \OCP\DB\Exception
+        */
+       private function getIncomplete() {
+               $query = $this->connection->getQueryBuilder();
+               $query->select('path')
+                       ->from('filecache')
+                       ->where($query->expr()->eq('storage', $query->createNamedParameter($this->cache->getNumericStorageId(), IQueryBuilder::PARAM_INT)))
+                       ->andWhere($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
+                       ->orderBy('path', 'DESC')
+                       ->setMaxResults(1);
+
+               $result = $query->executeQuery();
+               $path = $result->fetchOne();
+               $result->closeCursor();
+
+               if ($path === false) {
+                       return false;
+               }
+
+               // Make sure Oracle does not continue with null for empty strings
+               return (string)$path;
+       }
+}
index 921c50fd9582820d4af8ec53058abf62cf017aa1..ea60de137d25e3520e2d47a8f00abb259cd48acd 100644 (file)
@@ -163,14 +163,14 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common implements IChunkedFil
         *
         * @param string $path
         * @param \OC\Files\Storage\Storage (optional) the storage to pass to the scanner
-        * @return \OC\Files\ObjectStore\NoopScanner
+        * @return \OC\Files\ObjectStore\ObjectStoreScanner
         */
        public function getScanner($path = '', $storage = null) {
                if (!$storage) {
                        $storage = $this;
                }
                if (!isset($this->scanner)) {
-                       $this->scanner = new NoopScanner($storage);
+                       $this->scanner = new ObjectStoreScanner($storage);
                }
                return $this->scanner;
        }
diff --git a/tests/lib/Files/ObjectStore/NoopScannerTest.php b/tests/lib/Files/ObjectStore/NoopScannerTest.php
deleted file mode 100644 (file)
index 7142fb6..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-/**
- * ownCloud
- *
- * @author Joas Schilling
- * @copyright 2015 Joas Schilling nickvergessen@owncloud.com
- *
- * This file is licensed under the Affero General Public License version 3 or
- * later.
- * See the COPYING-README file.
- */
-
-namespace Test\Files\ObjectStore;
-
-class NoopScannerTest extends \Test\TestCase {
-       /** @var \OC\Files\Storage\Storage $storage */
-       private $storage;
-
-       /** @var \OC\Files\ObjectStore\NoopScanner $scanner */
-       private $scanner;
-
-       protected function setUp(): void {
-               parent::setUp();
-
-               $this->storage = new \OC\Files\Storage\Temporary([]);
-               $this->scanner = new \OC\Files\ObjectStore\NoopScanner($this->storage);
-       }
-
-       public function testFile() {
-               $data = "dummy file data\n";
-               $this->storage->file_put_contents('foo.txt', $data);
-
-               $this->assertEquals(
-                       [],
-                       $this->scanner->scanFile('foo.txt'),
-                       'Asserting that no error occurred while scanFile()'
-               );
-       }
-
-       private function fillTestFolders() {
-               $textData = "dummy file data\n";
-               $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo/logo.png');
-               $this->storage->mkdir('folder');
-               $this->storage->file_put_contents('foo.txt', $textData);
-               $this->storage->file_put_contents('foo.png', $imgData);
-               $this->storage->file_put_contents('folder/bar.txt', $textData);
-       }
-
-       public function testFolder() {
-               $this->fillTestFolders();
-
-               $this->assertEquals(
-                       [],
-                       $this->scanner->scan(''),
-                       'Asserting that no error occurred while scan()'
-               );
-       }
-
-       public function testBackgroundScan() {
-               $this->fillTestFolders();
-               $this->storage->mkdir('folder2');
-               $this->storage->file_put_contents('folder2/bar.txt', 'foobar');
-
-               $this->assertEquals(
-                       [],
-                       $this->scanner->scan('', \OC\Files\Cache\Scanner::SCAN_SHALLOW),
-                       'Asserting that no error occurred while scan(SCAN_SHALLOW)'
-               );
-
-               $this->scanner->backgroundScan();
-
-               $this->assertTrue(
-                       true,
-                       'Asserting that no error occurred while backgroundScan()'
-               );
-       }
-}
diff --git a/tests/lib/Files/ObjectStore/ObjectStoreScannerTest.php b/tests/lib/Files/ObjectStore/ObjectStoreScannerTest.php
new file mode 100644 (file)
index 0000000..02d72de
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+/**
+ * ownCloud
+ *
+ * @author Joas Schilling
+ * @copyright 2015 Joas Schilling nickvergessen@owncloud.com
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace Test\Files\ObjectStore;
+
+use OC\Files\Cache\Scanner;
+use OC\Files\ObjectStore\ObjectStoreScanner;
+use OC\Files\Storage\Temporary;
+use OCP\Files\Cache\ICache;
+use OCP\Files\Storage\IStorage;
+use Test\TestCase;
+
+/**
+ * @group DB
+ */
+class ObjectStoreScannerTest extends TestCase {
+       private IStorage $storage;
+       private ICache $cache;
+       private ObjectStoreScanner $scanner;
+       private Scanner $realScanner;
+
+       protected function setUp(): void {
+               parent::setUp();
+
+               $this->storage = new Temporary([]);
+               $this->cache = $this->storage->getCache();
+               $this->scanner = new ObjectStoreScanner($this->storage);
+               $this->realScanner = new Scanner($this->storage);
+       }
+
+       public function testFile() {
+               $data = "dummy file data\n";
+               $this->storage->file_put_contents('foo.txt', $data);
+
+               $this->assertEquals(
+                       [],
+                       $this->scanner->scanFile('foo.txt'),
+                       'Asserting that no error occurred while scanFile()'
+               );
+       }
+
+       private function fillTestFolders() {
+               $textData = "dummy file data\n";
+               $imgData = file_get_contents(\OC::$SERVERROOT . '/core/img/logo/logo.png');
+               $this->storage->mkdir('folder');
+               $this->storage->file_put_contents('foo.txt', $textData);
+               $this->storage->file_put_contents('foo.png', $imgData);
+               $this->storage->file_put_contents('folder/bar.txt', $textData);
+       }
+
+       public function testFolder() {
+               $this->fillTestFolders();
+
+               $this->assertEquals(
+                       [],
+                       $this->scanner->scan(''),
+                       'Asserting that no error occurred while scan()'
+               );
+       }
+
+       public function testBackgroundScan() {
+               $this->fillTestFolders();
+               $this->storage->mkdir('folder2');
+               $this->storage->file_put_contents('folder2/bar.txt', 'foobar');
+
+               $this->realScanner->scan('');
+
+               $this->assertEquals(6, $this->cache->get('folder2')->getSize());
+
+               $this->cache->put('folder2', ['size' => -1]);
+
+               $this->assertEquals(-1, $this->cache->get('folder2')->getSize());
+
+               $this->scanner->backgroundScan();
+
+               $this->assertEquals(6, $this->cache->get('folder2')->getSize());
+       }
+}