Skip to content

Commit d56988c

Browse files
committed
feat: move primary object store configuration to a single place
Signed-off-by: Robin Appelman <[email protected]>
1 parent b146b9f commit d56988c

File tree

10 files changed

+319
-273
lines changed

10 files changed

+319
-273
lines changed

build/psalm-baseline.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2055,14 +2055,6 @@
20552055
<code><![CDATA[wrap]]></code>
20562056
</UndefinedInterfaceMethod>
20572057
</file>
2058-
<file src="lib/private/Files/Mount/ObjectHomeMountProvider.php">
2059-
<InvalidNullableReturnType>
2060-
<code><![CDATA[\OCP\Files\Mount\IMountPoint]]></code>
2061-
</InvalidNullableReturnType>
2062-
<NullableReturnStatement>
2063-
<code><![CDATA[null]]></code>
2064-
</NullableReturnStatement>
2065-
</file>
20662058
<file src="lib/private/Files/Node/File.php">
20672059
<InvalidReturnStatement>
20682060
<code><![CDATA[$this->view->hash($type, $this->path, $raw)]]></code>

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,7 @@
14401440
'OC\\Files\\ObjectStore\\Mapper' => $baseDir . '/lib/private/Files/ObjectStore/Mapper.php',
14411441
'OC\\Files\\ObjectStore\\ObjectStoreScanner' => $baseDir . '/lib/private/Files/ObjectStore/ObjectStoreScanner.php',
14421442
'OC\\Files\\ObjectStore\\ObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php',
1443+
'OC\\Files\\ObjectStore\\PrimaryObjectStoreConfig' => $baseDir . '/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php',
14431444
'OC\\Files\\ObjectStore\\S3' => $baseDir . '/lib/private/Files/ObjectStore/S3.php',
14441445
'OC\\Files\\ObjectStore\\S3ConfigTrait' => $baseDir . '/lib/private/Files/ObjectStore/S3ConfigTrait.php',
14451446
'OC\\Files\\ObjectStore\\S3ConnectionTrait' => $baseDir . '/lib/private/Files/ObjectStore/S3ConnectionTrait.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,6 +1473,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
14731473
'OC\\Files\\ObjectStore\\Mapper' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Mapper.php',
14741474
'OC\\Files\\ObjectStore\\ObjectStoreScanner' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/ObjectStoreScanner.php',
14751475
'OC\\Files\\ObjectStore\\ObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php',
1476+
'OC\\Files\\ObjectStore\\PrimaryObjectStoreConfig' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php',
14761477
'OC\\Files\\ObjectStore\\S3' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3.php',
14771478
'OC\\Files\\ObjectStore\\S3ConfigTrait' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3ConfigTrait.php',
14781479
'OC\\Files\\ObjectStore\\S3ConnectionTrait' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3ConnectionTrait.php',

lib/private/Files/Mount/ObjectHomeMountProvider.php

Lines changed: 16 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -24,117 +24,39 @@
2424
*/
2525
namespace OC\Files\Mount;
2626

27+
use OC\Files\ObjectStore\HomeObjectStoreStorage;
28+
use OC\Files\ObjectStore\PrimaryObjectStoreConfig;
2729
use OCP\Files\Config\IHomeMountProvider;
30+
use OCP\Files\Mount\IMountPoint;
2831
use OCP\Files\Storage\IStorageFactory;
29-
use OCP\IConfig;
3032
use OCP\IUser;
31-
use Psr\Log\LoggerInterface;
3233

3334
/**
3435
* Mount provider for object store home storages
3536
*/
3637
class ObjectHomeMountProvider implements IHomeMountProvider {
37-
/**
38-
* @var IConfig
39-
*/
40-
private $config;
41-
42-
/**
43-
* ObjectStoreHomeMountProvider constructor.
44-
*
45-
* @param IConfig $config
46-
*/
47-
public function __construct(IConfig $config) {
48-
$this->config = $config;
38+
public function __construct(
39+
private PrimaryObjectStoreConfig $objectStoreConfig,
40+
) {
4941
}
5042

5143
/**
52-
* Get the cache mount for a user
44+
* Get the home mount for a user
5345
*
5446
* @param IUser $user
5547
* @param IStorageFactory $loader
56-
* @return \OCP\Files\Mount\IMountPoint
48+
* @return ?IMountPoint
5749
*/
58-
public function getHomeMountForUser(IUser $user, IStorageFactory $loader) {
59-
$config = $this->getMultiBucketObjectStoreConfig($user);
60-
if ($config === null) {
61-
$config = $this->getSingleBucketObjectStoreConfig($user);
62-
}
63-
64-
if ($config === null) {
50+
public function getHomeMountForUser(IUser $user, IStorageFactory $loader): ?IMountPoint {
51+
$objectStoreConfig = $this->objectStoreConfig->getObjectStoreConfigForUser($user);
52+
if ($objectStoreConfig === null) {
6553
return null;
6654
}
55+
$arguments = array_merge($objectStoreConfig['arguments'], [
56+
'objectstore' => $this->objectStoreConfig->buildObjectStore($objectStoreConfig),
57+
'user' => $user,
58+
]);
6759

68-
return new HomeMountPoint($user, '\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class);
69-
}
70-
71-
/**
72-
* @param IUser $user
73-
* @return array|null
74-
*/
75-
private function getSingleBucketObjectStoreConfig(IUser $user) {
76-
$config = $this->config->getSystemValue('objectstore');
77-
if (!is_array($config)) {
78-
return null;
79-
}
80-
81-
// sanity checks
82-
if (empty($config['class'])) {
83-
\OC::$server->get(LoggerInterface::class)->error('No class given for objectstore', ['app' => 'files']);
84-
}
85-
if (!isset($config['arguments'])) {
86-
$config['arguments'] = [];
87-
}
88-
// instantiate object store implementation
89-
$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
90-
91-
$config['arguments']['user'] = $user;
92-
93-
return $config;
94-
}
95-
96-
/**
97-
* @param IUser $user
98-
* @return array|null
99-
*/
100-
private function getMultiBucketObjectStoreConfig(IUser $user) {
101-
$config = $this->config->getSystemValue('objectstore_multibucket');
102-
if (!is_array($config)) {
103-
return null;
104-
}
105-
106-
// sanity checks
107-
if (empty($config['class'])) {
108-
\OC::$server->get(LoggerInterface::class)->error('No class given for objectstore', ['app' => 'files']);
109-
}
110-
if (!isset($config['arguments'])) {
111-
$config['arguments'] = [];
112-
}
113-
114-
$bucket = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null);
115-
116-
if ($bucket === null) {
117-
/*
118-
* Use any provided bucket argument as prefix
119-
* and add the mapping from username => bucket
120-
*/
121-
if (!isset($config['arguments']['bucket'])) {
122-
$config['arguments']['bucket'] = '';
123-
}
124-
$mapper = new \OC\Files\ObjectStore\Mapper($user, $this->config);
125-
$numBuckets = $config['arguments']['num_buckets'] ?? 64;
126-
$config['arguments']['bucket'] .= $mapper->getBucket($numBuckets);
127-
128-
$this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $config['arguments']['bucket']);
129-
} else {
130-
$config['arguments']['bucket'] = $bucket;
131-
}
132-
133-
// instantiate object store implementation
134-
$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
135-
136-
$config['arguments']['user'] = $user;
137-
138-
return $config;
60+
return new HomeMountPoint($user, HomeObjectStoreStorage::class, '/' . $user->getUID(), $arguments, $loader, null, null, self::class);
13961
}
14062
}

lib/private/Files/Mount/RootMountProvider.php

Lines changed: 12 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -25,79 +25,41 @@
2525

2626
use OC;
2727
use OC\Files\ObjectStore\ObjectStoreStorage;
28+
use OC\Files\ObjectStore\PrimaryObjectStoreConfig;
2829
use OC\Files\Storage\LocalRootStorage;
29-
use OC_App;
3030
use OCP\Files\Config\IRootMountProvider;
3131
use OCP\Files\Storage\IStorageFactory;
3232
use OCP\IConfig;
33-
use Psr\Log\LoggerInterface;
3433

3534
class RootMountProvider implements IRootMountProvider {
35+
private PrimaryObjectStoreConfig $objectStoreConfig;
3636
private IConfig $config;
37-
private LoggerInterface $logger;
3837

39-
public function __construct(IConfig $config, LoggerInterface $logger) {
38+
public function __construct(PrimaryObjectStoreConfig $objectStoreConfig, IConfig $config) {
39+
$this->objectStoreConfig = $objectStoreConfig;
4040
$this->config = $config;
41-
$this->logger = $logger;
4241
}
4342

4443
public function getRootMounts(IStorageFactory $loader): array {
45-
$objectStore = $this->config->getSystemValue('objectstore', null);
46-
$objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);
44+
$objectStoreConfig = $this->objectStoreConfig->getObjectStoreConfigForRoot();
4745

48-
if ($objectStoreMultiBucket) {
49-
return [$this->getMultiBucketStoreRootMount($loader, $objectStoreMultiBucket)];
50-
} elseif ($objectStore) {
51-
return [$this->getObjectStoreRootMount($loader, $objectStore)];
46+
if ($objectStoreConfig) {
47+
return [$this->getObjectStoreRootMount($loader, $objectStoreConfig)];
5248
} else {
5349
return [$this->getLocalRootMount($loader)];
5450
}
5551
}
5652

57-
private function validateObjectStoreConfig(array &$config) {
58-
if (empty($config['class'])) {
59-
$this->logger->error('No class given for objectstore', ['app' => 'files']);
60-
}
61-
if (!isset($config['arguments'])) {
62-
$config['arguments'] = [];
63-
}
64-
65-
// instantiate object store implementation
66-
$name = $config['class'];
67-
if (str_starts_with($name, 'OCA\\') && substr_count($name, '\\') >= 2) {
68-
$segments = explode('\\', $name);
69-
OC_App::loadApp(strtolower($segments[1]));
70-
}
71-
}
72-
7353
private function getLocalRootMount(IStorageFactory $loader): MountPoint {
7454
$configDataDirectory = $this->config->getSystemValue("datadirectory", OC::$SERVERROOT . "/data");
7555
return new MountPoint(LocalRootStorage::class, '/', ['datadir' => $configDataDirectory], $loader, null, null, self::class);
7656
}
7757

78-
private function getObjectStoreRootMount(IStorageFactory $loader, array $config): MountPoint {
79-
$this->validateObjectStoreConfig($config);
80-
81-
$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
82-
// mount with plain / root object store implementation
83-
$config['class'] = ObjectStoreStorage::class;
84-
85-
return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class);
86-
}
87-
88-
private function getMultiBucketStoreRootMount(IStorageFactory $loader, array $config): MountPoint {
89-
$this->validateObjectStoreConfig($config);
90-
91-
if (!isset($config['arguments']['bucket'])) {
92-
$config['arguments']['bucket'] = '';
93-
}
94-
// put the root FS always in first bucket for multibucket configuration
95-
$config['arguments']['bucket'] .= '0';
96-
97-
$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
98-
// mount with plain / root object store implementation
99-
$config['class'] = ObjectStoreStorage::class;
58+
private function getObjectStoreRootMount(IStorageFactory $loader, array $objectStoreConfig): MountPoint {
59+
$arguments = array_merge($objectStoreConfig['arguments'], [
60+
'objectstore' => $this->objectStoreConfig->buildObjectStore($objectStoreConfig),
61+
]);
10062

101-
return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class);
63+
return new MountPoint(ObjectStoreStorage::class, '/', $arguments, $loader, null, null, self::class);
10264
}
10365
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-only
7+
*/
8+
9+
namespace OC\Files\ObjectStore;
10+
11+
use OCP\App\IAppManager;
12+
use OCP\Files\ObjectStore\IObjectStore;
13+
use OCP\IConfig;
14+
use OCP\IUser;
15+
16+
/**
17+
* @psalm-type ObjectStoreConfig array{class: class-string<IObjectStore>, arguments: array{multibucket: bool, ...}}
18+
*/
19+
class PrimaryObjectStoreConfig {
20+
public function __construct(
21+
private IConfig $config,
22+
private IAppManager $appManager,
23+
) {
24+
}
25+
26+
/**
27+
* @param ObjectStoreConfig $config
28+
*/
29+
public function buildObjectStore(array $config): IObjectStore {
30+
return new $config['class']($config['arguments']);
31+
}
32+
33+
/**
34+
* @return ?ObjectStoreConfig
35+
*/
36+
public function getObjectStoreConfigForRoot(): ?array {
37+
$config = $this->getObjectStoreConfig();
38+
39+
if ($config && $config['arguments']['multibucket']) {
40+
if (!isset($config['arguments']['bucket'])) {
41+
$config['arguments']['bucket'] = '';
42+
}
43+
44+
// put the root FS always in first bucket for multibucket configuration
45+
$config['arguments']['bucket'] .= '0';
46+
}
47+
return $config;
48+
}
49+
50+
/**
51+
* @return ?ObjectStoreConfig
52+
*/
53+
public function getObjectStoreConfigForUser(IUser $user): ?array {
54+
$config = $this->getObjectStoreConfig();
55+
56+
if ($config && $config['arguments']['multibucket']) {
57+
$config['arguments']['bucket'] = $this->getBucketForUser($user, $config);
58+
}
59+
return $config;
60+
}
61+
62+
/**
63+
* @return ?ObjectStoreConfig
64+
*/
65+
private function getObjectStoreConfig(): ?array {
66+
$objectStore = $this->config->getSystemValue('objectstore', null);
67+
$objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);
68+
69+
// new-style multibucket config uses the same 'objectstore' key but sets `'multibucket' => true`, transparently upgrade older style config
70+
if ($objectStoreMultiBucket) {
71+
$objectStoreMultiBucket['arguments']['multibucket'] = true;
72+
return $this->validateObjectStoreConfig($objectStoreMultiBucket);
73+
} elseif ($objectStore) {
74+
return $this->validateObjectStoreConfig($objectStore);
75+
} else {
76+
return null;
77+
}
78+
}
79+
80+
/**
81+
* @return ObjectStoreConfig
82+
*/
83+
private function validateObjectStoreConfig(array $config) {
84+
if (!isset($config['class'])) {
85+
throw new \Exception('No class configured for object store');
86+
}
87+
if (!isset($config['arguments'])) {
88+
$config['arguments'] = [];
89+
}
90+
$class = $config['class'];
91+
$arguments = $config['arguments'];
92+
if (!is_array($arguments)) {
93+
throw new \Exception('Configured object store arguments are not an array');
94+
}
95+
if (!isset($arguments['multibucket'])) {
96+
$arguments['multibucket'] = false;
97+
}
98+
if (!is_bool($arguments['multibucket'])) {
99+
throw new \Exception('arguments.multibucket must be a boolean in object store configuration');
100+
}
101+
102+
if (!is_string($class)) {
103+
throw new \Exception('Configured class for object store is not a string');
104+
}
105+
106+
if (str_starts_with($class, 'OCA\\') && substr_count($class, '\\') >= 2) {
107+
[$appId] = explode('\\', $class);
108+
$this->appManager->loadApp(strtolower($appId));
109+
}
110+
111+
if (!is_a($class, IObjectStore::class, true)) {
112+
throw new \Exception('Configured class for object store is not an object store');
113+
}
114+
return [
115+
'class' => $class,
116+
'arguments' => $arguments,
117+
];
118+
}
119+
120+
private function getBucketForUser(IUser $user, array $config): string {
121+
$bucket = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null);
122+
123+
if ($bucket === null) {
124+
/*
125+
* Use any provided bucket argument as prefix
126+
* and add the mapping from username => bucket
127+
*/
128+
if (!isset($config['arguments']['bucket'])) {
129+
$config['arguments']['bucket'] = '';
130+
}
131+
$mapper = new Mapper($user, $this->config);
132+
$numBuckets = isset($config['arguments']['num_buckets']) ? $config['arguments']['num_buckets'] : 64;
133+
$bucket = $config['arguments']['bucket'] . $mapper->getBucket($numBuckets);
134+
135+
$this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $bucket);
136+
}
137+
138+
return $bucket;
139+
}
140+
}

0 commit comments

Comments
 (0)