aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Files/Mount
diff options
context:
space:
mode:
Diffstat (limited to 'lib/private/Files/Mount')
-rw-r--r--lib/private/Files/Mount/CacheMountProvider.php57
-rw-r--r--lib/private/Files/Mount/HomeMountPoint.php34
-rw-r--r--lib/private/Files/Mount/LocalHomeMountProvider.php29
-rw-r--r--lib/private/Files/Mount/Manager.php235
-rw-r--r--lib/private/Files/Mount/MountPoint.php293
-rw-r--r--lib/private/Files/Mount/MoveableMount.php30
-rw-r--r--lib/private/Files/Mount/ObjectHomeMountProvider.php45
-rw-r--r--lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php138
-rw-r--r--lib/private/Files/Mount/RootMountProvider.php50
9 files changed, 911 insertions, 0 deletions
diff --git a/lib/private/Files/Mount/CacheMountProvider.php b/lib/private/Files/Mount/CacheMountProvider.php
new file mode 100644
index 00000000000..27c7eec9da3
--- /dev/null
+++ b/lib/private/Files/Mount/CacheMountProvider.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Files\Mount;
+
+use OCP\Files\Config\IMountProvider;
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IConfig;
+use OCP\IUser;
+
+/**
+ * Mount provider for custom cache storages
+ */
+class CacheMountProvider implements IMountProvider {
+ /**
+ * @var IConfig
+ */
+ private $config;
+
+ /**
+ * ObjectStoreHomeMountProvider constructor.
+ *
+ * @param IConfig $config
+ */
+ public function __construct(IConfig $config) {
+ $this->config = $config;
+ }
+
+ /**
+ * Get the cache mount for a user
+ *
+ * @param IUser $user
+ * @param IStorageFactory $loader
+ * @return \OCP\Files\Mount\IMountPoint[]
+ */
+ public function getMountsForUser(IUser $user, IStorageFactory $loader) {
+ $cacheBaseDir = $this->config->getSystemValueString('cache_path', '');
+ if ($cacheBaseDir !== '') {
+ $cacheDir = rtrim($cacheBaseDir, '/') . '/' . $user->getUID();
+ if (!file_exists($cacheDir)) {
+ mkdir($cacheDir, 0770, true);
+ mkdir($cacheDir . '/uploads', 0770, true);
+ }
+
+ return [
+ new MountPoint('\OC\Files\Storage\Local', '/' . $user->getUID() . '/cache', ['datadir' => $cacheDir], $loader, null, null, self::class),
+ new MountPoint('\OC\Files\Storage\Local', '/' . $user->getUID() . '/uploads', ['datadir' => $cacheDir . '/uploads'], $loader, null, null, self::class)
+ ];
+ } else {
+ return [];
+ }
+ }
+}
diff --git a/lib/private/Files/Mount/HomeMountPoint.php b/lib/private/Files/Mount/HomeMountPoint.php
new file mode 100644
index 00000000000..5a648f08c89
--- /dev/null
+++ b/lib/private/Files/Mount/HomeMountPoint.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Files\Mount;
+
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IUser;
+
+class HomeMountPoint extends MountPoint {
+ private IUser $user;
+
+ public function __construct(
+ IUser $user,
+ $storage,
+ string $mountpoint,
+ ?array $arguments = null,
+ ?IStorageFactory $loader = null,
+ ?array $mountOptions = null,
+ ?int $mountId = null,
+ ?string $mountProvider = null,
+ ) {
+ parent::__construct($storage, $mountpoint, $arguments, $loader, $mountOptions, $mountId, $mountProvider);
+ $this->user = $user;
+ }
+
+ public function getUser(): IUser {
+ return $this->user;
+ }
+}
diff --git a/lib/private/Files/Mount/LocalHomeMountProvider.php b/lib/private/Files/Mount/LocalHomeMountProvider.php
new file mode 100644
index 00000000000..a2b3d3b2a99
--- /dev/null
+++ b/lib/private/Files/Mount/LocalHomeMountProvider.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Files\Mount;
+
+use OCP\Files\Config\IHomeMountProvider;
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IUser;
+
+/**
+ * Mount provider for regular posix home folders
+ */
+class LocalHomeMountProvider implements IHomeMountProvider {
+ /**
+ * Get the cache mount for a user
+ *
+ * @param IUser $user
+ * @param IStorageFactory $loader
+ * @return \OCP\Files\Mount\IMountPoint|null
+ */
+ public function getHomeMountForUser(IUser $user, IStorageFactory $loader) {
+ $arguments = ['user' => $user];
+ return new HomeMountPoint($user, '\OC\Files\Storage\Home', '/' . $user->getUID(), $arguments, $loader, null, null, self::class);
+ }
+}
diff --git a/lib/private/Files/Mount/Manager.php b/lib/private/Files/Mount/Manager.php
new file mode 100644
index 00000000000..55de488c726
--- /dev/null
+++ b/lib/private/Files/Mount/Manager.php
@@ -0,0 +1,235 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Files\Mount;
+
+use OC\Files\Filesystem;
+use OC\Files\SetupManager;
+use OC\Files\SetupManagerFactory;
+use OCP\Cache\CappedMemoryCache;
+use OCP\Files\Config\ICachedMountInfo;
+use OCP\Files\Mount\IMountManager;
+use OCP\Files\Mount\IMountPoint;
+use OCP\Files\NotFoundException;
+
+class Manager implements IMountManager {
+ /** @var MountPoint[] */
+ private array $mounts = [];
+ /** @var CappedMemoryCache<IMountPoint> */
+ private CappedMemoryCache $pathCache;
+ /** @var CappedMemoryCache<IMountPoint[]> */
+ private CappedMemoryCache $inPathCache;
+ private SetupManager $setupManager;
+
+ public function __construct(SetupManagerFactory $setupManagerFactory) {
+ $this->pathCache = new CappedMemoryCache();
+ $this->inPathCache = new CappedMemoryCache();
+ $this->setupManager = $setupManagerFactory->create($this);
+ }
+
+ /**
+ * @param IMountPoint $mount
+ */
+ public function addMount(IMountPoint $mount) {
+ $this->mounts[$mount->getMountPoint()] = $mount;
+ $this->pathCache->clear();
+ $this->inPathCache->clear();
+ }
+
+ /**
+ * @param string $mountPoint
+ */
+ public function removeMount(string $mountPoint) {
+ $mountPoint = Filesystem::normalizePath($mountPoint);
+ if (\strlen($mountPoint) > 1) {
+ $mountPoint .= '/';
+ }
+ unset($this->mounts[$mountPoint]);
+ $this->pathCache->clear();
+ $this->inPathCache->clear();
+ }
+
+ /**
+ * @param string $mountPoint
+ * @param string $target
+ */
+ public function moveMount(string $mountPoint, string $target) {
+ $this->mounts[$target] = $this->mounts[$mountPoint];
+ unset($this->mounts[$mountPoint]);
+ $this->pathCache->clear();
+ $this->inPathCache->clear();
+ }
+
+ /**
+ * Find the mount for $path
+ *
+ * @param string $path
+ * @return IMountPoint
+ */
+ public function find(string $path): IMountPoint {
+ $this->setupManager->setupForPath($path);
+ $path = Filesystem::normalizePath($path);
+
+ if (isset($this->pathCache[$path])) {
+ return $this->pathCache[$path];
+ }
+
+
+
+ if (count($this->mounts) === 0) {
+ $this->setupManager->setupRoot();
+ if (count($this->mounts) === 0) {
+ throw new \Exception('No mounts even after explicitly setting up the root mounts');
+ }
+ }
+
+ $current = $path;
+ while (true) {
+ $mountPoint = $current . '/';
+ if (isset($this->mounts[$mountPoint])) {
+ $this->pathCache[$path] = $this->mounts[$mountPoint];
+ return $this->mounts[$mountPoint];
+ } elseif ($current === '') {
+ break;
+ }
+
+ $current = dirname($current);
+ if ($current === '.' || $current === '/') {
+ $current = '';
+ }
+ }
+
+ throw new NotFoundException('No mount for path ' . $path . ' existing mounts (' . count($this->mounts) . '): ' . implode(',', array_keys($this->mounts)));
+ }
+
+ /**
+ * Find all mounts in $path
+ *
+ * @param string $path
+ * @return IMountPoint[]
+ */
+ public function findIn(string $path): array {
+ $this->setupManager->setupForPath($path, true);
+ $path = $this->formatPath($path);
+
+ if (isset($this->inPathCache[$path])) {
+ return $this->inPathCache[$path];
+ }
+
+ $result = [];
+ $pathLength = \strlen($path);
+ $mountPoints = array_keys($this->mounts);
+ foreach ($mountPoints as $mountPoint) {
+ if (substr($mountPoint, 0, $pathLength) === $path && \strlen($mountPoint) > $pathLength) {
+ $result[] = $this->mounts[$mountPoint];
+ }
+ }
+
+ $this->inPathCache[$path] = $result;
+ return $result;
+ }
+
+ public function clear() {
+ $this->mounts = [];
+ $this->pathCache->clear();
+ $this->inPathCache->clear();
+ }
+
+ /**
+ * Find mounts by storage id
+ *
+ * @param string $id
+ * @return IMountPoint[]
+ */
+ public function findByStorageId(string $id): array {
+ if (\strlen($id) > 64) {
+ $id = md5($id);
+ }
+ $result = [];
+ foreach ($this->mounts as $mount) {
+ if ($mount->getStorageId() === $id) {
+ $result[] = $mount;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * @return IMountPoint[]
+ */
+ public function getAll(): array {
+ return $this->mounts;
+ }
+
+ /**
+ * Find mounts by numeric storage id
+ *
+ * @param int $id
+ * @return IMountPoint[]
+ */
+ public function findByNumericId(int $id): array {
+ $result = [];
+ foreach ($this->mounts as $mount) {
+ if ($mount->getNumericStorageId() === $id) {
+ $result[] = $mount;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * @param string $path
+ * @return string
+ */
+ private function formatPath(string $path): string {
+ $path = Filesystem::normalizePath($path);
+ if (\strlen($path) > 1) {
+ $path .= '/';
+ }
+ return $path;
+ }
+
+ public function getSetupManager(): SetupManager {
+ return $this->setupManager;
+ }
+
+ /**
+ * Return all mounts in a path from a specific mount provider
+ *
+ * @param string $path
+ * @param string[] $mountProviders
+ * @return MountPoint[]
+ */
+ public function getMountsByMountProvider(string $path, array $mountProviders) {
+ $this->getSetupManager()->setupForProvider($path, $mountProviders);
+ if (in_array('', $mountProviders)) {
+ return $this->mounts;
+ } else {
+ return array_filter($this->mounts, function ($mount) use ($mountProviders) {
+ return in_array($mount->getMountProvider(), $mountProviders);
+ });
+ }
+ }
+
+ /**
+ * Return the mount matching a cached mount info (or mount file info)
+ *
+ * @param ICachedMountInfo $info
+ *
+ * @return IMountPoint|null
+ */
+ public function getMountFromMountInfo(ICachedMountInfo $info): ?IMountPoint {
+ $this->setupManager->setupForPath($info->getMountPoint());
+ foreach ($this->mounts as $mount) {
+ if ($mount->getMountPoint() === $info->getMountPoint()) {
+ return $mount;
+ }
+ }
+ return null;
+ }
+}
diff --git a/lib/private/Files/Mount/MountPoint.php b/lib/private/Files/Mount/MountPoint.php
new file mode 100644
index 00000000000..bab2dc8e4bd
--- /dev/null
+++ b/lib/private/Files/Mount/MountPoint.php
@@ -0,0 +1,293 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Files\Mount;
+
+use OC\Files\Filesystem;
+use OC\Files\Storage\Storage;
+use OC\Files\Storage\StorageFactory;
+use OCP\Files\Mount\IMountPoint;
+use OCP\Files\Storage\IStorageFactory;
+use Psr\Log\LoggerInterface;
+
+class MountPoint implements IMountPoint {
+ /**
+ * @var \OC\Files\Storage\Storage|null $storage
+ */
+ protected $storage = null;
+ protected $class;
+ protected $storageId;
+ protected $numericStorageId = null;
+ protected $rootId = null;
+
+ /**
+ * Configuration options for the storage backend
+ *
+ * @var array
+ */
+ protected $arguments = [];
+ protected $mountPoint;
+
+ /**
+ * Mount specific options
+ *
+ * @var array
+ */
+ protected $mountOptions = [];
+
+ /**
+ * @var \OC\Files\Storage\StorageFactory $loader
+ */
+ private $loader;
+
+ /**
+ * Specified whether the storage is invalid after failing to
+ * instantiate it.
+ *
+ * @var bool
+ */
+ private $invalidStorage = false;
+
+ /** @var int|null */
+ protected $mountId;
+
+ /** @var string */
+ protected $mountProvider;
+
+ /**
+ * @param string|\OC\Files\Storage\Storage $storage
+ * @param string $mountpoint
+ * @param array $arguments (optional) configuration for the storage backend
+ * @param \OCP\Files\Storage\IStorageFactory $loader
+ * @param array $mountOptions mount specific options
+ * @param int|null $mountId
+ * @param string|null $mountProvider
+ * @throws \Exception
+ */
+ public function __construct(
+ $storage,
+ string $mountpoint,
+ ?array $arguments = null,
+ ?IStorageFactory $loader = null,
+ ?array $mountOptions = null,
+ ?int $mountId = null,
+ ?string $mountProvider = null,
+ ) {
+ if (is_null($arguments)) {
+ $arguments = [];
+ }
+ if (is_null($loader)) {
+ $this->loader = new StorageFactory();
+ } else {
+ $this->loader = $loader;
+ }
+
+ if (!is_null($mountOptions)) {
+ $this->mountOptions = $mountOptions;
+ }
+
+ $mountpoint = $this->formatPath($mountpoint);
+ $this->mountPoint = $mountpoint;
+ $this->mountId = $mountId;
+ if ($storage instanceof Storage) {
+ $this->class = get_class($storage);
+ $this->storage = $this->loader->wrap($this, $storage);
+ } else {
+ // Update old classes to new namespace
+ if (str_contains($storage, 'OC_Filestorage_')) {
+ $storage = '\OC\Files\Storage\\' . substr($storage, 15);
+ }
+ $this->class = $storage;
+ $this->arguments = $arguments;
+ }
+ if ($mountProvider) {
+ if (strlen($mountProvider) > 128) {
+ throw new \Exception("Mount provider $mountProvider name exceeds the limit of 128 characters");
+ }
+ }
+ $this->mountProvider = $mountProvider ?? '';
+ }
+
+ /**
+ * get complete path to the mount point, relative to data/
+ *
+ * @return string
+ */
+ public function getMountPoint() {
+ return $this->mountPoint;
+ }
+
+ /**
+ * Sets the mount point path, relative to data/
+ *
+ * @param string $mountPoint new mount point
+ */
+ public function setMountPoint($mountPoint) {
+ $this->mountPoint = $this->formatPath($mountPoint);
+ }
+
+ /**
+ * create the storage that is mounted
+ */
+ private function createStorage() {
+ if ($this->invalidStorage) {
+ return;
+ }
+
+ if (class_exists($this->class)) {
+ try {
+ $class = $this->class;
+ // prevent recursion by setting the storage before applying wrappers
+ $this->storage = new $class($this->arguments);
+ $this->storage = $this->loader->wrap($this, $this->storage);
+ } catch (\Exception $exception) {
+ $this->storage = null;
+ $this->invalidStorage = true;
+ if ($this->mountPoint === '/') {
+ // the root storage could not be initialized, show the user!
+ throw new \Exception('The root storage could not be initialized. Please contact your local administrator.', $exception->getCode(), $exception);
+ } else {
+ \OC::$server->get(LoggerInterface::class)->error($exception->getMessage(), ['exception' => $exception]);
+ }
+ return;
+ }
+ } else {
+ \OC::$server->get(LoggerInterface::class)->error('Storage backend ' . $this->class . ' not found', ['app' => 'core']);
+ $this->invalidStorage = true;
+ return;
+ }
+ }
+
+ /**
+ * @return \OC\Files\Storage\Storage|null
+ */
+ public function getStorage() {
+ if (is_null($this->storage)) {
+ $this->createStorage();
+ }
+ return $this->storage;
+ }
+
+ /**
+ * @return string|null
+ */
+ public function getStorageId() {
+ if (!$this->storageId) {
+ $storage = $this->getStorage();
+ if (is_null($storage)) {
+ return null;
+ }
+ $this->storageId = $storage->getId();
+ if (strlen($this->storageId) > 64) {
+ $this->storageId = md5($this->storageId);
+ }
+ }
+ return $this->storageId;
+ }
+
+ /**
+ * @return int
+ */
+ public function getNumericStorageId() {
+ if (is_null($this->numericStorageId)) {
+ $storage = $this->getStorage();
+ if (is_null($storage)) {
+ return -1;
+ }
+ $this->numericStorageId = $storage->getCache()->getNumericStorageId();
+ }
+ return $this->numericStorageId;
+ }
+
+ /**
+ * @param string $path
+ * @return string
+ */
+ public function getInternalPath($path) {
+ $path = Filesystem::normalizePath($path, true, false, true);
+ if ($this->mountPoint === $path or $this->mountPoint . '/' === $path) {
+ $internalPath = '';
+ } else {
+ $internalPath = substr($path, strlen($this->mountPoint));
+ }
+ // substr returns false instead of an empty string, we always want a string
+ return (string)$internalPath;
+ }
+
+ /**
+ * @param string $path
+ * @return string
+ */
+ private function formatPath($path) {
+ $path = Filesystem::normalizePath($path);
+ if (strlen($path) > 1) {
+ $path .= '/';
+ }
+ return $path;
+ }
+
+ /**
+ * @param callable $wrapper
+ */
+ public function wrapStorage($wrapper) {
+ $storage = $this->getStorage();
+ // storage can be null if it couldn't be initialized
+ if ($storage != null) {
+ $this->storage = $wrapper($this->mountPoint, $storage, $this);
+ }
+ }
+
+ /**
+ * Get a mount option
+ *
+ * @param string $name Name of the mount option to get
+ * @param mixed $default Default value for the mount option
+ * @return mixed
+ */
+ public function getOption($name, $default) {
+ return $this->mountOptions[$name] ?? $default;
+ }
+
+ /**
+ * Get all options for the mount
+ *
+ * @return array
+ */
+ public function getOptions() {
+ return $this->mountOptions;
+ }
+
+ /**
+ * Get the file id of the root of the storage
+ *
+ * @return int
+ */
+ public function getStorageRootId() {
+ if (is_null($this->rootId) || $this->rootId === -1) {
+ $storage = $this->getStorage();
+ // if we can't create the storage return -1 as root id, this is then handled the same as if the root isn't scanned yet
+ if ($storage === null) {
+ $this->rootId = -1;
+ } else {
+ $this->rootId = (int)$storage->getCache()->getId('');
+ }
+ }
+ return $this->rootId;
+ }
+
+ public function getMountId() {
+ return $this->mountId;
+ }
+
+ public function getMountType() {
+ return '';
+ }
+
+ public function getMountProvider(): string {
+ return $this->mountProvider;
+ }
+}
diff --git a/lib/private/Files/Mount/MoveableMount.php b/lib/private/Files/Mount/MoveableMount.php
new file mode 100644
index 00000000000..755733bf651
--- /dev/null
+++ b/lib/private/Files/Mount/MoveableMount.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Files\Mount;
+
+use OCP\Files\Mount\IMovableMount;
+
+/**
+ * Defines the mount point to be (re)moved by the user
+ */
+interface MoveableMount extends IMovableMount {
+ /**
+ * Move the mount point to $target
+ *
+ * @param string $target the target mount point
+ * @return bool
+ */
+ public function moveMount($target);
+
+ /**
+ * Remove the mount points
+ *
+ * @return bool
+ */
+ public function removeMount();
+}
diff --git a/lib/private/Files/Mount/ObjectHomeMountProvider.php b/lib/private/Files/Mount/ObjectHomeMountProvider.php
new file mode 100644
index 00000000000..4b088f2c808
--- /dev/null
+++ b/lib/private/Files/Mount/ObjectHomeMountProvider.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+namespace OC\Files\Mount;
+
+use OC\Files\ObjectStore\HomeObjectStoreStorage;
+use OC\Files\ObjectStore\PrimaryObjectStoreConfig;
+use OCP\Files\Config\IHomeMountProvider;
+use OCP\Files\Mount\IMountPoint;
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IUser;
+
+/**
+ * Mount provider for object store home storages
+ */
+class ObjectHomeMountProvider implements IHomeMountProvider {
+ public function __construct(
+ private PrimaryObjectStoreConfig $objectStoreConfig,
+ ) {
+ }
+
+ /**
+ * Get the home mount for a user
+ *
+ * @param IUser $user
+ * @param IStorageFactory $loader
+ * @return ?IMountPoint
+ */
+ public function getHomeMountForUser(IUser $user, IStorageFactory $loader): ?IMountPoint {
+ $objectStoreConfig = $this->objectStoreConfig->getObjectStoreConfigForUser($user);
+ if ($objectStoreConfig === null) {
+ return null;
+ }
+ $arguments = array_merge($objectStoreConfig['arguments'], [
+ 'objectstore' => $this->objectStoreConfig->buildObjectStore($objectStoreConfig),
+ 'user' => $user,
+ ]);
+
+ return new HomeMountPoint($user, HomeObjectStoreStorage::class, '/' . $user->getUID(), $arguments, $loader, null, null, self::class);
+ }
+}
diff --git a/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php b/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php
new file mode 100644
index 00000000000..1546ef98f50
--- /dev/null
+++ b/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php
@@ -0,0 +1,138 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+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 Psr\Log\LoggerInterface;
+
+/**
+ * Mount provider for object store app data folder for previews
+ */
+class ObjectStorePreviewCacheMountProvider implements IRootMountProvider {
+ private LoggerInterface $logger;
+ /** @var IConfig */
+ private $config;
+
+ public function __construct(LoggerInterface $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,
+ null,
+ null,
+ self::class
+ );
+ $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,
+ null,
+ null,
+ self::class
+ );
+
+ 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/Mount/RootMountProvider.php b/lib/private/Files/Mount/RootMountProvider.php
new file mode 100644
index 00000000000..5e0c924ad38
--- /dev/null
+++ b/lib/private/Files/Mount/RootMountProvider.php
@@ -0,0 +1,50 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OC\Files\Mount;
+
+use OC;
+use OC\Files\ObjectStore\ObjectStoreStorage;
+use OC\Files\ObjectStore\PrimaryObjectStoreConfig;
+use OC\Files\Storage\LocalRootStorage;
+use OCP\Files\Config\IRootMountProvider;
+use OCP\Files\Storage\IStorageFactory;
+use OCP\IConfig;
+
+class RootMountProvider implements IRootMountProvider {
+ private PrimaryObjectStoreConfig $objectStoreConfig;
+ private IConfig $config;
+
+ public function __construct(PrimaryObjectStoreConfig $objectStoreConfig, IConfig $config) {
+ $this->objectStoreConfig = $objectStoreConfig;
+ $this->config = $config;
+ }
+
+ public function getRootMounts(IStorageFactory $loader): array {
+ $objectStoreConfig = $this->objectStoreConfig->getObjectStoreConfigForRoot();
+
+ if ($objectStoreConfig) {
+ return [$this->getObjectStoreRootMount($loader, $objectStoreConfig)];
+ } else {
+ return [$this->getLocalRootMount($loader)];
+ }
+ }
+
+ private function getLocalRootMount(IStorageFactory $loader): MountPoint {
+ $configDataDirectory = $this->config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data');
+ return new MountPoint(LocalRootStorage::class, '/', ['datadir' => $configDataDirectory], $loader, null, null, self::class);
+ }
+
+ private function getObjectStoreRootMount(IStorageFactory $loader, array $objectStoreConfig): MountPoint {
+ $arguments = array_merge($objectStoreConfig['arguments'], [
+ 'objectstore' => $this->objectStoreConfig->buildObjectStore($objectStoreConfig),
+ ]);
+
+ return new MountPoint(ObjectStoreStorage::class, '/', $arguments, $loader, null, null, self::class);
+ }
+}