Skip to content

Commit 1884b83

Browse files
icewind1991backportbot[bot]
authored andcommitted
feat: move primary object store configuration to a single place
Signed-off-by: Robin Appelman <robin@icewind.nl> [skip ci]
1 parent afcb36f commit 1884b83

File tree

10 files changed

+243
-226
lines changed

10 files changed

+243
-226
lines changed

build/psalm-baseline.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,14 +1975,6 @@
19751975
<code><![CDATA[wrap]]></code>
19761976
</UndefinedInterfaceMethod>
19771977
</file>
1978-
<file src="lib/private/Files/Mount/ObjectHomeMountProvider.php">
1979-
<InvalidNullableReturnType>
1980-
<code><![CDATA[\OCP\Files\Mount\IMountPoint]]></code>
1981-
</InvalidNullableReturnType>
1982-
<NullableReturnStatement>
1983-
<code><![CDATA[null]]></code>
1984-
</NullableReturnStatement>
1985-
</file>
19861978
<file src="lib/private/Files/Node/File.php">
19871979
<InvalidReturnStatement>
19881980
<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
@@ -1565,6 +1565,7 @@
15651565
'OC\\Files\\ObjectStore\\Mapper' => $baseDir . '/lib/private/Files/ObjectStore/Mapper.php',
15661566
'OC\\Files\\ObjectStore\\ObjectStoreScanner' => $baseDir . '/lib/private/Files/ObjectStore/ObjectStoreScanner.php',
15671567
'OC\\Files\\ObjectStore\\ObjectStoreStorage' => $baseDir . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php',
1568+
'OC\\Files\\ObjectStore\\PrimaryObjectStoreConfig' => $baseDir . '/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php',
15681569
'OC\\Files\\ObjectStore\\S3' => $baseDir . '/lib/private/Files/ObjectStore/S3.php',
15691570
'OC\\Files\\ObjectStore\\S3ConfigTrait' => $baseDir . '/lib/private/Files/ObjectStore/S3ConfigTrait.php',
15701571
'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
@@ -1598,6 +1598,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
15981598
'OC\\Files\\ObjectStore\\Mapper' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/Mapper.php',
15991599
'OC\\Files\\ObjectStore\\ObjectStoreScanner' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/ObjectStoreScanner.php',
16001600
'OC\\Files\\ObjectStore\\ObjectStoreStorage' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/ObjectStoreStorage.php',
1601+
'OC\\Files\\ObjectStore\\PrimaryObjectStoreConfig' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php',
16011602
'OC\\Files\\ObjectStore\\S3' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3.php',
16021603
'OC\\Files\\ObjectStore\\S3ConfigTrait' => __DIR__ . '/../../..' . '/lib/private/Files/ObjectStore/S3ConfigTrait.php',
16031604
'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
@@ -7,117 +7,39 @@
77
*/
88
namespace OC\Files\Mount;
99

10+
use OC\Files\ObjectStore\HomeObjectStoreStorage;
11+
use OC\Files\ObjectStore\PrimaryObjectStoreConfig;
1012
use OCP\Files\Config\IHomeMountProvider;
13+
use OCP\Files\Mount\IMountPoint;
1114
use OCP\Files\Storage\IStorageFactory;
12-
use OCP\IConfig;
1315
use OCP\IUser;
14-
use Psr\Log\LoggerInterface;
1516

1617
/**
1718
* Mount provider for object store home storages
1819
*/
1920
class ObjectHomeMountProvider implements IHomeMountProvider {
20-
/**
21-
* @var IConfig
22-
*/
23-
private $config;
24-
25-
/**
26-
* ObjectStoreHomeMountProvider constructor.
27-
*
28-
* @param IConfig $config
29-
*/
30-
public function __construct(IConfig $config) {
31-
$this->config = $config;
21+
public function __construct(
22+
private PrimaryObjectStoreConfig $objectStoreConfig,
23+
) {
3224
}
3325

3426
/**
35-
* Get the cache mount for a user
27+
* Get the home mount for a user
3628
*
3729
* @param IUser $user
3830
* @param IStorageFactory $loader
39-
* @return \OCP\Files\Mount\IMountPoint
31+
* @return ?IMountPoint
4032
*/
41-
public function getHomeMountForUser(IUser $user, IStorageFactory $loader) {
42-
$config = $this->getMultiBucketObjectStoreConfig($user);
43-
if ($config === null) {
44-
$config = $this->getSingleBucketObjectStoreConfig($user);
45-
}
46-
47-
if ($config === null) {
33+
public function getHomeMountForUser(IUser $user, IStorageFactory $loader): ?IMountPoint {
34+
$objectStoreConfig = $this->objectStoreConfig->getObjectStoreConfigForUser($user);
35+
if ($objectStoreConfig === null) {
4836
return null;
4937
}
38+
$arguments = array_merge($objectStoreConfig['arguments'], [
39+
'objectstore' => $this->objectStoreConfig->buildObjectStore($objectStoreConfig),
40+
'user' => $user,
41+
]);
5042

51-
return new HomeMountPoint($user, '\OC\Files\ObjectStore\HomeObjectStoreStorage', '/' . $user->getUID(), $config['arguments'], $loader, null, null, self::class);
52-
}
53-
54-
/**
55-
* @param IUser $user
56-
* @return array|null
57-
*/
58-
private function getSingleBucketObjectStoreConfig(IUser $user) {
59-
$config = $this->config->getSystemValue('objectstore');
60-
if (!is_array($config)) {
61-
return null;
62-
}
63-
64-
// sanity checks
65-
if (empty($config['class'])) {
66-
\OC::$server->get(LoggerInterface::class)->error('No class given for objectstore', ['app' => 'files']);
67-
}
68-
if (!isset($config['arguments'])) {
69-
$config['arguments'] = [];
70-
}
71-
// instantiate object store implementation
72-
$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
73-
74-
$config['arguments']['user'] = $user;
75-
76-
return $config;
77-
}
78-
79-
/**
80-
* @param IUser $user
81-
* @return array|null
82-
*/
83-
private function getMultiBucketObjectStoreConfig(IUser $user) {
84-
$config = $this->config->getSystemValue('objectstore_multibucket');
85-
if (!is_array($config)) {
86-
return null;
87-
}
88-
89-
// sanity checks
90-
if (empty($config['class'])) {
91-
\OC::$server->get(LoggerInterface::class)->error('No class given for objectstore', ['app' => 'files']);
92-
}
93-
if (!isset($config['arguments'])) {
94-
$config['arguments'] = [];
95-
}
96-
97-
$bucket = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null);
98-
99-
if ($bucket === null) {
100-
/*
101-
* Use any provided bucket argument as prefix
102-
* and add the mapping from username => bucket
103-
*/
104-
if (!isset($config['arguments']['bucket'])) {
105-
$config['arguments']['bucket'] = '';
106-
}
107-
$mapper = new \OC\Files\ObjectStore\Mapper($user, $this->config);
108-
$numBuckets = $config['arguments']['num_buckets'] ?? 64;
109-
$config['arguments']['bucket'] .= $mapper->getBucket($numBuckets);
110-
111-
$this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $config['arguments']['bucket']);
112-
} else {
113-
$config['arguments']['bucket'] = $bucket;
114-
}
115-
116-
// instantiate object store implementation
117-
$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
118-
119-
$config['arguments']['user'] = $user;
120-
121-
return $config;
43+
return new HomeMountPoint($user, HomeObjectStoreStorage::class, '/' . $user->getUID(), $arguments, $loader, null, null, self::class);
12244
}
12345
}

lib/private/Files/Mount/RootMountProvider.php

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

1111
use OC;
1212
use OC\Files\ObjectStore\ObjectStoreStorage;
13+
use OC\Files\ObjectStore\PrimaryObjectStoreConfig;
1314
use OC\Files\Storage\LocalRootStorage;
14-
use OC_App;
1515
use OCP\Files\Config\IRootMountProvider;
1616
use OCP\Files\Storage\IStorageFactory;
1717
use OCP\IConfig;
18-
use Psr\Log\LoggerInterface;
1918

2019
class RootMountProvider implements IRootMountProvider {
20+
private PrimaryObjectStoreConfig $objectStoreConfig;
2121
private IConfig $config;
22-
private LoggerInterface $logger;
2322

24-
public function __construct(IConfig $config, LoggerInterface $logger) {
23+
public function __construct(PrimaryObjectStoreConfig $objectStoreConfig, IConfig $config) {
24+
$this->objectStoreConfig = $objectStoreConfig;
2525
$this->config = $config;
26-
$this->logger = $logger;
2726
}
2827

2928
public function getRootMounts(IStorageFactory $loader): array {
30-
$objectStore = $this->config->getSystemValue('objectstore', null);
31-
$objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);
29+
$objectStoreConfig = $this->objectStoreConfig->getObjectStoreConfigForRoot();
3230

33-
if ($objectStoreMultiBucket) {
34-
return [$this->getMultiBucketStoreRootMount($loader, $objectStoreMultiBucket)];
35-
} elseif ($objectStore) {
36-
return [$this->getObjectStoreRootMount($loader, $objectStore)];
31+
if ($objectStoreConfig) {
32+
return [$this->getObjectStoreRootMount($loader, $objectStoreConfig)];
3733
} else {
3834
return [$this->getLocalRootMount($loader)];
3935
}
4036
}
4137

42-
private function validateObjectStoreConfig(array &$config) {
43-
if (empty($config['class'])) {
44-
$this->logger->error('No class given for objectstore', ['app' => 'files']);
45-
}
46-
if (!isset($config['arguments'])) {
47-
$config['arguments'] = [];
48-
}
49-
50-
// instantiate object store implementation
51-
$name = $config['class'];
52-
if (str_starts_with($name, 'OCA\\') && substr_count($name, '\\') >= 2) {
53-
$segments = explode('\\', $name);
54-
OC_App::loadApp(strtolower($segments[1]));
55-
}
56-
}
57-
5838
private function getLocalRootMount(IStorageFactory $loader): MountPoint {
5939
$configDataDirectory = $this->config->getSystemValue('datadirectory', OC::$SERVERROOT . '/data');
6040
return new MountPoint(LocalRootStorage::class, '/', ['datadir' => $configDataDirectory], $loader, null, null, self::class);
6141
}
6242

63-
private function getObjectStoreRootMount(IStorageFactory $loader, array $config): MountPoint {
64-
$this->validateObjectStoreConfig($config);
65-
66-
$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
67-
// mount with plain / root object store implementation
68-
$config['class'] = ObjectStoreStorage::class;
69-
70-
return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class);
71-
}
72-
73-
private function getMultiBucketStoreRootMount(IStorageFactory $loader, array $config): MountPoint {
74-
$this->validateObjectStoreConfig($config);
75-
76-
if (!isset($config['arguments']['bucket'])) {
77-
$config['arguments']['bucket'] = '';
78-
}
79-
// put the root FS always in first bucket for multibucket configuration
80-
$config['arguments']['bucket'] .= '0';
81-
82-
$config['arguments']['objectstore'] = new $config['class']($config['arguments']);
83-
// mount with plain / root object store implementation
84-
$config['class'] = ObjectStoreStorage::class;
43+
private function getObjectStoreRootMount(IStorageFactory $loader, array $objectStoreConfig): MountPoint {
44+
$arguments = array_merge($objectStoreConfig['arguments'], [
45+
'objectstore' => $this->objectStoreConfig->buildObjectStore($objectStoreConfig),
46+
]);
8547

86-
return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class);
48+
return new MountPoint(ObjectStoreStorage::class, '/', $arguments, $loader, null, null, self::class);
8749
}
8850
}
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 readonly IConfig $config,
22+
private readonly 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)