diff --git a/config/config.sample.php b/config/config.sample.php
index e95f2535af81d1f62a2456487d23b2e494e6b46e..862c5ec1a1d6b195932eeb2ad715b0fa91f8ee56 100644
--- a/config/config.sample.php
+++ b/config/config.sample.php
@@ -1354,6 +1354,23 @@ $CONFIG = [
 	],
 ],
 
+/**
+ * If this is set to true and a multibucket object store is configured then
+ * newly created previews are put into 256 dedicated buckets.
+ *
+ * Those buckets are named like the mulibucket version but with the postfix
+ * ``-preview-NUMBER`` where NUMBER is between 0 and 255.
+ *
+ * Keep in mind that only previews of files are put in there that don't have
+ * some already. Otherwise the old bucket will be used.
+ *
+ * To migrate existing previews to this new multibucket distribution of previews
+ * use the occ command ``preview:repair``. For now this will only migrate
+ * previews that were generated before Nextcloud 19 in the flat
+ * ``appdata_INSTANCEID/previews/FILEID`` folder structure.
+ */
+'objectstore.multibucket.preview-distribution' => false,
+
 
 /**
  * Sharing
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 254840b3542dd137efb0ca36e2e752fa0efda6b1..65542e477e70524647006bd4c4eaad10d167e75c 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -230,6 +230,7 @@ return array(
     'OCP\\Files\\Config\\IHomeMountProvider' => $baseDir . '/lib/public/Files/Config/IHomeMountProvider.php',
     'OCP\\Files\\Config\\IMountProvider' => $baseDir . '/lib/public/Files/Config/IMountProvider.php',
     'OCP\\Files\\Config\\IMountProviderCollection' => $baseDir . '/lib/public/Files/Config/IMountProviderCollection.php',
+    'OCP\\Files\\Config\\IRootMountProvider' => $baseDir . '/lib/public/Files/Config/IRootMountProvider.php',
     'OCP\\Files\\Config\\IUserMountCache' => $baseDir . '/lib/public/Files/Config/IUserMountCache.php',
     'OCP\\Files\\EmptyFileNameException' => $baseDir . '/lib/public/Files/EmptyFileNameException.php',
     'OCP\\Files\\EntityTooLargeException' => $baseDir . '/lib/public/Files/EntityTooLargeException.php',
@@ -1029,6 +1030,7 @@ return array(
     'OC\\Files\\Mount\\MountPoint' => $baseDir . '/lib/private/Files/Mount/MountPoint.php',
     'OC\\Files\\Mount\\MoveableMount' => $baseDir . '/lib/private/Files/Mount/MoveableMount.php',
     'OC\\Files\\Mount\\ObjectHomeMountProvider' => $baseDir . '/lib/private/Files/Mount/ObjectHomeMountProvider.php',
+    'OC\\Files\\Mount\\ObjectStorePreviewCacheMountProvider' => $baseDir . '/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php',
     'OC\\Files\\Node\\File' => $baseDir . '/lib/private/Files/Node/File.php',
     'OC\\Files\\Node\\Folder' => $baseDir . '/lib/private/Files/Node/Folder.php',
     'OC\\Files\\Node\\HookConnector' => $baseDir . '/lib/private/Files/Node/HookConnector.php',
@@ -1040,6 +1042,7 @@ return array(
     'OC\\Files\\Node\\Root' => $baseDir . '/lib/private/Files/Node/Root.php',
     'OC\\Files\\Notify\\Change' => $baseDir . '/lib/private/Files/Notify/Change.php',
     'OC\\Files\\Notify\\RenameChange' => $baseDir . '/lib/private/Files/Notify/RenameChange.php',
+    'OC\\Files\\ObjectStore\\AppdataPreviewObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php',
     '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',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 45bf93241d5fa933928fbcb5022d470233f47a5a..835603a33403a1d9735f4fa43aea1ae86b1480c4 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -259,6 +259,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OCP\\Files\\Config\\IHomeMountProvider' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IHomeMountProvider.php',
         'OCP\\Files\\Config\\IMountProvider' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IMountProvider.php',
         'OCP\\Files\\Config\\IMountProviderCollection' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IMountProviderCollection.php',
+        'OCP\\Files\\Config\\IRootMountProvider' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IRootMountProvider.php',
         'OCP\\Files\\Config\\IUserMountCache' => __DIR__ . '/../../..' . '/lib/public/Files/Config/IUserMountCache.php',
         'OCP\\Files\\EmptyFileNameException' => __DIR__ . '/../../..' . '/lib/public/Files/EmptyFileNameException.php',
         'OCP\\Files\\EntityTooLargeException' => __DIR__ . '/../../..' . '/lib/public/Files/EntityTooLargeException.php',
@@ -1058,6 +1059,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Files\\Mount\\MountPoint' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/MountPoint.php',
         'OC\\Files\\Mount\\MoveableMount' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/MoveableMount.php',
         'OC\\Files\\Mount\\ObjectHomeMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/ObjectHomeMountProvider.php',
+        'OC\\Files\\Mount\\ObjectStorePreviewCacheMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php',
         'OC\\Files\\Node\\File' => __DIR__ . '/../../..' . '/lib/private/Files/Node/File.php',
         'OC\\Files\\Node\\Folder' => __DIR__ . '/../../..' . '/lib/private/Files/Node/Folder.php',
         'OC\\Files\\Node\\HookConnector' => __DIR__ . '/../../..' . '/lib/private/Files/Node/HookConnector.php',
@@ -1069,6 +1071,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Files\\Node\\Root' => __DIR__ . '/../../..' . '/lib/private/Files/Node/Root.php',
         'OC\\Files\\Notify\\Change' => __DIR__ . '/../../..' . '/lib/private/Files/Notify/Change.php',
         'OC\\Files\\Notify\\RenameChange' => __DIR__ . '/../../..' . '/lib/private/Files/Notify/RenameChange.php',
+        'OC\\Files\\ObjectStore\\AppdataPreviewObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php',
         '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',
diff --git a/lib/private/Files/Config/MountProviderCollection.php b/lib/private/Files/Config/MountProviderCollection.php
index 34db652290fd3259d1a23446cc9b4af3514ec11e..2b57ffe6e4c0d70e58892017946b56c8ecf03dad 100644
--- a/lib/private/Files/Config/MountProviderCollection.php
+++ b/lib/private/Files/Config/MountProviderCollection.php
@@ -30,6 +30,7 @@ use OC\Hooks\EmitterTrait;
 use OCP\Files\Config\IHomeMountProvider;
 use OCP\Files\Config\IMountProvider;
 use OCP\Files\Config\IMountProviderCollection;
+use OCP\Files\Config\IRootMountProvider;
 use OCP\Files\Config\IUserMountCache;
 use OCP\Files\Mount\IMountManager;
 use OCP\Files\Mount\IMountPoint;
@@ -49,6 +50,9 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
 	 */
 	private $providers = [];
 
+	/** @var \OCP\Files\Config\IRootMountProvider[] */
+	private $rootProviders = [];
+
 	/**
 	 * @var \OCP\Files\Storage\IStorageFactory
 	 */
@@ -198,4 +202,19 @@ class MountProviderCollection implements IMountProviderCollection, Emitter {
 	public function getMountCache() {
 		return $this->mountCache;
 	}
+
+	public function registerRootProvider(IRootMountProvider $provider) {
+		$this->rootProviders[] = $provider;
+	}
+
+	public function getRootMounts(): array {
+		$loader = $this->loader;
+		$mounts = array_map(function (IRootMountProvider $provider) use ($loader) {
+			return $provider->getRootMounts($loader);
+		}, $this->rootProviders);
+		$mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) {
+			return array_merge($mounts, $providerMounts);
+		}, []);
+		return $mounts;
+	}
 }
diff --git a/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php b/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..9bbb744bbcc23a4dce150dba003672d860237e23
--- /dev/null
+++ b/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php
@@ -0,0 +1,150 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020, Morris Jobke <hey@morrisjobke.de>
+ *
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Files\Mount;
+
+use OC\Files\ObjectStore\AppdataPreviewObjectStoreStorage;
+use OC\Files\ObjectStore\ObjectStoreStorage;
+use OC\Files\Storage\Wrapper\Jail;
+use OCP\Files\Config\IRootMountProvider;
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IConfig;
+use OCP\ILogger;
+
+/**
+ * Mount provider for object store app data folder for previews
+ */
+class ObjectStorePreviewCacheMountProvider implements IRootMountProvider {
+	/** @var ILogger */
+	private $logger;
+	/** @var IConfig */
+	private $config;
+
+	public function __construct(ILogger $logger, IConfig $config) {
+		$this->logger = $logger;
+		$this->config = $config;
+	}
+
+	/**
+	 * @return MountPoint[]
+	 * @throws \Exception
+	 */
+	public function getRootMounts(IStorageFactory $loader): array {
+		if (!is_array($this->config->getSystemValue('objectstore_multibucket'))) {
+			return [];
+		}
+		if ($this->config->getSystemValue('objectstore.multibucket.preview-distribution', false) !== true) {
+			return [];
+		}
+
+		$instanceId = $this->config->getSystemValueString('instanceid', '');
+		$mountPoints = [];
+		$directoryRange = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
+		$i = 0;
+		foreach ($directoryRange as $parent) {
+			foreach ($directoryRange as $child) {
+				$mountPoints[] = new MountPoint(
+					AppdataPreviewObjectStoreStorage::class,
+					'/appdata_' . $instanceId . '/preview/' . $parent . '/' . $child,
+					$this->getMultiBucketObjectStore($i),
+					$loader
+				);
+				$i++;
+			}
+		}
+
+		$rootStorageArguments = $this->getMultiBucketObjectStoreForRoot();
+		$fakeRootStorage = new ObjectStoreStorage($rootStorageArguments);
+		$fakeRootStorageJail = new Jail([
+			'storage' => $fakeRootStorage,
+			'root' => '/appdata_' . $instanceId . '/preview',
+		]);
+
+		// add a fallback location to be able to fetch existing previews from the old bucket
+		$mountPoints[] = new MountPoint(
+			$fakeRootStorageJail,
+			'/appdata_' . $instanceId . '/preview/old-multibucket',
+			null,
+			$loader
+		);
+
+		return $mountPoints;
+	}
+
+	protected function getMultiBucketObjectStore(int $number): array {
+		$config = $this->config->getSystemValue('objectstore_multibucket');
+
+		// sanity checks
+		if (empty($config['class'])) {
+			$this->logger->error('No class given for objectstore', ['app' => 'files']);
+		}
+		if (!isset($config['arguments'])) {
+			$config['arguments'] = [];
+		}
+
+		/*
+		 * Use any provided bucket argument as prefix
+		 * and add the mapping from parent/child => bucket
+		 */
+		if (!isset($config['arguments']['bucket'])) {
+			$config['arguments']['bucket'] = '';
+		}
+
+		$config['arguments']['bucket'] .= "-preview-$number";
+
+		// instantiate object store implementation
+		$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
+
+		$config['arguments']['internal-id'] = $number;
+
+		return $config['arguments'];
+	}
+
+	protected function getMultiBucketObjectStoreForRoot(): array {
+		$config = $this->config->getSystemValue('objectstore_multibucket');
+
+		// sanity checks
+		if (empty($config['class'])) {
+			$this->logger->error('No class given for objectstore', ['app' => 'files']);
+		}
+		if (!isset($config['arguments'])) {
+			$config['arguments'] = [];
+		}
+
+		/*
+		 * Use any provided bucket argument as prefix
+		 * and add the mapping from parent/child => bucket
+		 */
+		if (!isset($config['arguments']['bucket'])) {
+			$config['arguments']['bucket'] = '';
+		}
+		$config['arguments']['bucket'] .= '0';
+
+		// instantiate object store implementation
+		$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
+
+		return $config['arguments'];
+	}
+}
diff --git a/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php b/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..ab6cb07cdc3d00fd93c2d3746bac647360452d51
--- /dev/null
+++ b/lib/private/Files/ObjectStore/AppdataPreviewObjectStoreStorage.php
@@ -0,0 +1,44 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020, Morris Jobke <hey@morrisjobke.de>
+ *
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Files\ObjectStore;
+
+class AppdataPreviewObjectStoreStorage extends ObjectStoreStorage {
+
+	/** @var string */
+	private $internalId;
+
+	public function __construct($params) {
+		if (!isset($params['internal-id'])) {
+			throw new \Exception('missing id in parameters');
+		}
+		$this->internalId = (string)$params['internal-id'];
+		parent::__construct($params);
+	}
+
+	public function getId() {
+		return 'object::appdata::preview:' . $this->internalId;
+	}
+}
diff --git a/lib/private/Preview/Storage/Root.php b/lib/private/Preview/Storage/Root.php
index a9a72026a5180cd28a9e0667bde1180c21a86eda..a284b037b3558ff1d228c3822d3c3a39ee719597 100644
--- a/lib/private/Preview/Storage/Root.php
+++ b/lib/private/Preview/Storage/Root.php
@@ -1,7 +1,6 @@
 <?php
 
 declare(strict_types=1);
-
 /**
  * @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
  *
@@ -33,14 +32,25 @@ use OCP\Files\NotFoundException;
 use OCP\Files\SimpleFS\ISimpleFolder;
 
 class Root extends AppData {
+	private $isMultibucketPreviewDistributionEnabled = false;
 	public function __construct(IRootFolder $rootFolder, SystemConfig $systemConfig) {
 		parent::__construct($rootFolder, $systemConfig, 'preview');
+
+		$this->isMultibucketPreviewDistributionEnabled = $systemConfig->getValue('objectstore.multibucket.preview-distribution', false) === true;
 	}
 
 
 	public function getFolder(string $name): ISimpleFolder {
 		$internalFolder = $this->getInternalFolder($name);
 
+		if ($this->isMultibucketPreviewDistributionEnabled) {
+			try {
+				return parent::getFolder('old-multibucket/' . $internalFolder);
+			} catch (NotFoundException $e) {
+				// not in multibucket fallback
+			}
+		}
+
 		try {
 			return parent::getFolder($internalFolder);
 		} catch (NotFoundException $e) {
diff --git a/lib/private/Server.php b/lib/private/Server.php
index e054d8230d9a389a97524ff7e3f907068570cefb..8d771bec3a1ff18c76d8daea962d8f4cffa3ba5f 100644
--- a/lib/private/Server.php
+++ b/lib/private/Server.php
@@ -88,6 +88,7 @@ use OC\Files\Config\UserMountCacheListener;
 use OC\Files\Mount\CacheMountProvider;
 use OC\Files\Mount\LocalHomeMountProvider;
 use OC\Files\Mount\ObjectHomeMountProvider;
+use OC\Files\Mount\ObjectStorePreviewCacheMountProvider;
 use OC\Files\Node\HookConnector;
 use OC\Files\Node\LazyRoot;
 use OC\Files\Node\Root;
@@ -904,9 +905,11 @@ class Server extends ServerContainer implements IServerContainer {
 			// builtin providers
 
 			$config = $c->getConfig();
+			$logger = $c->getLogger();
 			$manager->registerProvider(new CacheMountProvider($config));
 			$manager->registerHomeProvider(new LocalHomeMountProvider());
 			$manager->registerHomeProvider(new ObjectHomeMountProvider($config));
+			$manager->registerRootProvider(new ObjectStorePreviewCacheMountProvider($logger, $config));
 
 			return $manager;
 		});
diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php
index ab386ab61727ce8a1856a3fa6440ef193c286045..fd55962447e23f1681a3a9ad5ef745c00166554e 100644
--- a/lib/private/legacy/OC_Util.php
+++ b/lib/private/legacy/OC_Util.php
@@ -297,6 +297,17 @@ class OC_Util {
 			self::initLocalStorageRootFS();
 		}
 
+		/** @var \OCP\Files\Config\IMountProviderCollection $mountProviderCollection */
+		$mountProviderCollection = \OC::$server->query(\OCP\Files\Config\IMountProviderCollection::class);
+		/** @var \OCP\Files\Mount\IMountPoint[] $rootMountProviders */
+		$rootMountProviders = $mountProviderCollection->getRootMounts();
+
+		/** @var \OC\Files\Mount\Manager $mountManager */
+		$mountManager = \OC\Files\Filesystem::getMountManager();
+		foreach ($rootMountProviders as $rootMountProvider) {
+			$mountManager->addMount($rootMountProvider);
+		}
+
 		if ($user != '' && !\OC::$server->getUserManager()->userExists($user)) {
 			\OC::$server->getEventLogger()->end('setup_fs');
 			return false;
diff --git a/lib/public/Files/Config/IRootMountProvider.php b/lib/public/Files/Config/IRootMountProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..0f7b0eca3d4b148e3a934a84016a3b1e177ff6dd
--- /dev/null
+++ b/lib/public/Files/Config/IRootMountProvider.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020, Morris Jobke <hey@morrisjobke.de>
+ *
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\Files\Config;
+
+use OCP\Files\Storage\IStorageFactory;
+
+/**
+ * @since 20.0.0
+ */
+interface IRootMountProvider {
+	/**
+	 * Get all root mountpoints
+	 *
+	 * @return \OCP\Files\Mount\IMountPoint[]
+	 * @since 20.0.0
+	 */
+	public function getRootMounts(IStorageFactory $loader): array;
+}
diff --git a/tests/lib/Files/Mount/ObjectStorePreviewCacheMountProviderTest.php b/tests/lib/Files/Mount/ObjectStorePreviewCacheMountProviderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..400808d7cd5ea761e7af9e7ca14b371c2106c422
--- /dev/null
+++ b/tests/lib/Files/Mount/ObjectStorePreviewCacheMountProviderTest.php
@@ -0,0 +1,113 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020, Morris Jobke <hey@morrisjobke.de>
+ *
+ * @author Morris Jobke <hey@morrisjobke.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\Files\Mount;
+
+use OC\Files\Mount\ObjectStorePreviewCacheMountProvider;
+use OC\Files\ObjectStore\S3;
+use OC\Files\Storage\StorageFactory;
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IConfig;
+use OCP\ILogger;
+use PHPUnit\Framework\MockObject\MockObject;
+
+/**
+ * @group DB
+ *
+ * The DB permission is needed for the fake root storage initialization
+ */
+class ObjectStorePreviewCacheMountProviderTest extends \Test\TestCase {
+
+	/** @var ObjectStorePreviewCacheMountProvider */
+	protected $provider;
+
+	/** @var ILogger|MockObject */
+	protected $logger;
+	/** @var IConfig|MockObject */
+	protected $config;
+	/** @var IStorageFactory|MockObject */
+	protected $loader;
+
+
+	protected function setUp(): void {
+		parent::setUp();
+
+		$this->logger = $this->createMock(ILogger::class);
+		$this->config = $this->createMock(IConfig::class);
+		$this->loader = $this->createMock(StorageFactory::class);
+
+		$this->provider = new ObjectStorePreviewCacheMountProvider($this->logger, $this->config);
+	}
+
+	public function testNoMultibucketObjectStorage() {
+		$this->config->expects($this->once())
+			->method('getSystemValue')
+			->with('objectstore_multibucket')
+			->willReturn(null);
+
+		$this->assertEquals([], $this->provider->getRootMounts($this->loader));
+	}
+
+	public function testMultibucketObjectStorage() {
+		$objectstoreConfig = [
+			'class' => S3::class,
+			'arguments' => [
+				'bucket' => 'abc',
+				'num_buckets' => 64,
+				'key' => 'KEY',
+				'secret' => 'SECRET',
+				'hostname' => 'IP',
+				'port' => 'PORT',
+				'use_ssl' => false,
+				'use_path_style' => true,
+			],
+		];
+		$this->config->expects($this->any())
+			->method('getSystemValue')
+			->willReturnCallback(function ($config) use ($objectstoreConfig) {
+				if ($config === 'objectstore_multibucket') {
+					return $objectstoreConfig;
+				} elseif ($config === 'objectstore.multibucket.preview-distribution') {
+					return true;
+				}
+				return null;
+			});
+		$this->config->expects($this->once())
+			->method('getSystemValueString')
+			->with('instanceid')
+			->willReturn('INSTANCEID');
+
+		$mounts = $this->provider->getRootMounts($this->loader);
+
+		// 256 mounts for the subfolders and 1 for the fake root
+		$this->assertCount(257, $mounts);
+
+		// do some sanity checks if they have correct mount point paths
+		$this->assertEquals('/appdata_INSTANCEID/preview/0/0/', $mounts[0]->getMountPoint());
+		$this->assertEquals('/appdata_INSTANCEID/preview/2/5/', $mounts[37]->getMountPoint());
+		// also test the path of the fake bucket
+		$this->assertEquals('/appdata_INSTANCEID/preview/old-multibucket/', $mounts[256]->getMountPoint());
+	}
+}