Skip to content

Commit 56169fd

Browse files
committed
feat: move primary object store configuration to a single place
Signed-off-by: Robin Appelman <robin@icewind.nl>
1 parent 1950076 commit 56169fd

10 files changed

Lines changed: 314 additions & 264 deletions

File tree

build/psalm-baseline.xml

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

0 commit comments

Comments
 (0)