aboutsummaryrefslogtreecommitdiffstats
path: root/lib/private/Files/ObjectStore/PrimaryObjectStoreConfig.php
blob: fdfe989addca7bf9c53d11fc258ece5101fc82db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
<?php

declare(strict_types=1);
/**
 * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-only
 */

namespace OC\Files\ObjectStore;

use OCP\App\IAppManager;
use OCP\Files\ObjectStore\IObjectStore;
use OCP\IConfig;
use OCP\IUser;

/**
 * @psalm-type ObjectStoreConfig array{class: class-string<IObjectStore>, arguments: array{multibucket: bool, ...}}
 */
class PrimaryObjectStoreConfig {
	public function __construct(
		private readonly IConfig $config,
		private readonly IAppManager $appManager,
	) {
	}

	/**
	 * @param ObjectStoreConfig $config
	 */
	public function buildObjectStore(array $config): IObjectStore {
		return new $config['class']($config['arguments']);
	}

	/**
	 * @return ?ObjectStoreConfig
	 */
	public function getObjectStoreConfigForRoot(): ?array {
		$config = $this->getObjectStoreConfig();

		if ($config && $config['arguments']['multibucket']) {
			if (!isset($config['arguments']['bucket'])) {
				$config['arguments']['bucket'] = '';
			}

			// put the root FS always in first bucket for multibucket configuration
			$config['arguments']['bucket'] .= '0';
		}
		return $config;
	}

	/**
	 * @return ?ObjectStoreConfig
	 */
	public function getObjectStoreConfigForUser(IUser $user): ?array {
		$config = $this->getObjectStoreConfig();

		if ($config && $config['arguments']['multibucket']) {
			$config['arguments']['bucket'] = $this->getBucketForUser($user, $config);
		}
		return $config;
	}

	/**
	 * @return ?ObjectStoreConfig
	 */
	private function getObjectStoreConfig(): ?array {
		$objectStore = $this->config->getSystemValue('objectstore', null);
		$objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null);

		// new-style multibucket config uses the same 'objectstore' key but sets `'multibucket' => true`, transparently upgrade older style config
		if ($objectStoreMultiBucket) {
			$objectStoreMultiBucket['arguments']['multibucket'] = true;
			return $this->validateObjectStoreConfig($objectStoreMultiBucket);
		} elseif ($objectStore) {
			return $this->validateObjectStoreConfig($objectStore);
		} else {
			return null;
		}
	}

	/**
	 * @return ObjectStoreConfig
	 */
	private function validateObjectStoreConfig(array $config) {
		if (!isset($config['class'])) {
			throw new \Exception('No class configured for object store');
		}
		if (!isset($config['arguments'])) {
			$config['arguments'] = [];
		}
		$class = $config['class'];
		$arguments = $config['arguments'];
		if (!is_array($arguments)) {
			throw new \Exception('Configured object store arguments are not an array');
		}
		if (!isset($arguments['multibucket'])) {
			$arguments['multibucket'] = false;
		}
		if (!is_bool($arguments['multibucket'])) {
			throw new \Exception('arguments.multibucket must be a boolean in object store configuration');
		}

		if (!is_string($class)) {
			throw new \Exception('Configured class for object store is not a string');
		}

		if (str_starts_with($class, 'OCA\\') && substr_count($class, '\\') >= 2) {
			[$appId] = explode('\\', $class);
			$this->appManager->loadApp(strtolower($appId));
		}

		if (!is_a($class, IObjectStore::class, true)) {
			throw new \Exception('Configured class for object store is not an object store');
		}
		return [
			'class' => $class,
			'arguments' => $arguments,
		];
	}

	private function getBucketForUser(IUser $user, array $config): string {
		$bucket = $this->config->getUserValue($user->getUID(), 'homeobjectstore', 'bucket', null);

		if ($bucket === null) {
			/*
			 * Use any provided bucket argument as prefix
			 * and add the mapping from username => bucket
			 */
			if (!isset($config['arguments']['bucket'])) {
				$config['arguments']['bucket'] = '';
			}
			$mapper = new Mapper($user, $this->config);
			$numBuckets = isset($config['arguments']['num_buckets']) ? $config['arguments']['num_buckets'] : 64;
			$bucket = $config['arguments']['bucket'] . $mapper->getBucket($numBuckets);

			$this->config->setUserValue($user->getUID(), 'homeobjectstore', 'bucket', $bucket);
		}

		return $bucket;
	}
}