Signed-off-by: Robin Appelman <robin@icewind.nl>tags/v24.0.0beta1
@@ -1129,6 +1129,7 @@ return array( | |||
'OC\\Files\\Mount\\MoveableMount' => $baseDir . '/lib/private/Files/Mount/MoveableMount.php', | |||
'OC\\Files\\Mount\\ObjectHomeMountProvider' => $baseDir . '/lib/private/Files/Mount/ObjectHomeMountProvider.php', | |||
'OC\\Files\\Mount\\ObjectStorePreviewCacheMountProvider' => $baseDir . '/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php', | |||
'OC\\Files\\Mount\\RootMountProvider' => $baseDir . '/lib/private/Files/Mount/RootMountProvider.php', | |||
'OC\\Files\\Node\\File' => $baseDir . '/lib/private/Files/Node/File.php', | |||
'OC\\Files\\Node\\Folder' => $baseDir . '/lib/private/Files/Node/Folder.php', | |||
'OC\\Files\\Node\\HookConnector' => $baseDir . '/lib/private/Files/Node/HookConnector.php', |
@@ -1158,6 +1158,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OC\\Files\\Mount\\MoveableMount' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/MoveableMount.php', | |||
'OC\\Files\\Mount\\ObjectHomeMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/ObjectHomeMountProvider.php', | |||
'OC\\Files\\Mount\\ObjectStorePreviewCacheMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/ObjectStorePreviewCacheMountProvider.php', | |||
'OC\\Files\\Mount\\RootMountProvider' => __DIR__ . '/../../..' . '/lib/private/Files/Mount/RootMountProvider.php', | |||
'OC\\Files\\Node\\File' => __DIR__ . '/../../..' . '/lib/private/Files/Node/File.php', | |||
'OC\\Files\\Node\\Folder' => __DIR__ . '/../../..' . '/lib/private/Files/Node/Folder.php', | |||
'OC\\Files\\Node\\HookConnector' => __DIR__ . '/../../..' . '/lib/private/Files/Node/HookConnector.php', |
@@ -0,0 +1,103 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace OC\Files\Mount; | |||
use OC; | |||
use OC\Files\ObjectStore\ObjectStoreStorage; | |||
use OC\Files\Storage\LocalRootStorage; | |||
use OC_App; | |||
use OCP\Files\Config\IRootMountProvider; | |||
use OCP\Files\Storage\IStorageFactory; | |||
use OCP\IConfig; | |||
use Psr\Log\LoggerInterface; | |||
class RootMountProvider implements IRootMountProvider { | |||
private IConfig $config; | |||
private LoggerInterface $logger; | |||
public function __construct(IConfig $config, LoggerInterface $logger) { | |||
$this->config = $config; | |||
$this->logger = $logger; | |||
} | |||
public function getRootMounts(IStorageFactory $loader): array { | |||
$objectStore = $this->config->getSystemValue('objectstore', null); | |||
$objectStoreMultiBucket = $this->config->getSystemValue('objectstore_multibucket', null); | |||
if ($objectStoreMultiBucket) { | |||
return [$this->getMultiBucketStoreRootMount($loader, $objectStoreMultiBucket)]; | |||
} elseif ($objectStore) { | |||
return [$this->getObjectStoreRootMount($loader, $objectStore)]; | |||
} else { | |||
return [$this->getLocalRootMount($loader)]; | |||
} | |||
} | |||
private function validateObjectStoreConfig(array &$config) { | |||
if (empty($config['class'])) { | |||
$this->logger->error('No class given for objectstore', ['app' => 'files']); | |||
} | |||
if (!isset($config['arguments'])) { | |||
$config['arguments'] = []; | |||
} | |||
// instantiate object store implementation | |||
$name = $config['class']; | |||
if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) { | |||
$segments = explode('\\', $name); | |||
OC_App::loadApp(strtolower($segments[1])); | |||
} | |||
} | |||
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 $config): MountPoint { | |||
$this->validateObjectStoreConfig($config); | |||
$config['arguments']['objectstore'] = new $config['class']($config['arguments']); | |||
// mount with plain / root object store implementation | |||
$config['class'] = ObjectStoreStorage::class; | |||
return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class); | |||
} | |||
private function getMultiBucketStoreRootMount(IStorageFactory $loader, array $config): MountPoint { | |||
$this->validateObjectStoreConfig($config); | |||
if (!isset($config['arguments']['bucket'])) { | |||
$config['arguments']['bucket'] = ''; | |||
} | |||
// put the root FS always in first bucket for multibucket configuration | |||
$config['arguments']['bucket'] .= '0'; | |||
$config['arguments']['objectstore'] = new $config['class']($config['arguments']); | |||
// mount with plain / root object store implementation | |||
$config['class'] = ObjectStoreStorage::class; | |||
return new MountPoint($config['class'], '/', $config['arguments'], $loader, null, null, self::class); | |||
} | |||
} |
@@ -91,6 +91,7 @@ use OC\Files\Mount\CacheMountProvider; | |||
use OC\Files\Mount\LocalHomeMountProvider; | |||
use OC\Files\Mount\ObjectHomeMountProvider; | |||
use OC\Files\Mount\ObjectStorePreviewCacheMountProvider; | |||
use OC\Files\Mount\RootMountProvider; | |||
use OC\Files\Node\HookConnector; | |||
use OC\Files\Node\LazyRoot; | |||
use OC\Files\Node\Root; | |||
@@ -952,6 +953,7 @@ class Server extends ServerContainer implements IServerContainer { | |||
$manager->registerProvider(new CacheMountProvider($config)); | |||
$manager->registerHomeProvider(new LocalHomeMountProvider()); | |||
$manager->registerHomeProvider(new ObjectHomeMountProvider($config)); | |||
$manager->registerRootProvider(new RootMountProvider($config, $c->get(LoggerInterface::class))); | |||
$manager->registerRootProvider(new ObjectStorePreviewCacheMountProvider($logger, $config)); | |||
return $manager; |
@@ -66,11 +66,9 @@ | |||
use bantu\IniGetWrapper\IniGetWrapper; | |||
use OC\AppFramework\Http\Request; | |||
use OC\Files\Storage\LocalRootStorage; | |||
use OCP\Files\Template\ITemplateManager; | |||
use OCP\IConfig; | |||
use OCP\IGroupManager; | |||
use OCP\ILogger; | |||
use OCP\IURLGenerator; | |||
use OCP\IUser; | |||
use OCP\Share\IManager; | |||
@@ -80,7 +78,6 @@ class OC_Util { | |||
public static $scripts = []; | |||
public static $styles = []; | |||
public static $headers = []; | |||
private static $rootMounted = false; | |||
private static $rootFsSetup = false; | |||
private static $fsSetup = false; | |||
@@ -91,93 +88,6 @@ class OC_Util { | |||
return \OC::$server->getAppManager(); | |||
} | |||
private static function initLocalStorageRootFS() { | |||
// mount local file backend as root | |||
$configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data"); | |||
//first set up the local "root" storage | |||
\OC\Files\Filesystem::initMountManager(); | |||
if (!self::$rootMounted) { | |||
\OC\Files\Filesystem::mount(LocalRootStorage::class, ['datadir' => $configDataDirectory], '/'); | |||
self::$rootMounted = true; | |||
} | |||
} | |||
/** | |||
* mounting an object storage as the root fs will in essence remove the | |||
* necessity of a data folder being present. | |||
* TODO make home storage aware of this and use the object storage instead of local disk access | |||
* | |||
* @param array $config containing 'class' and optional 'arguments' | |||
* @suppress PhanDeprecatedFunction | |||
*/ | |||
private static function initObjectStoreRootFS($config) { | |||
// check misconfiguration | |||
if (empty($config['class'])) { | |||
\OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR); | |||
} | |||
if (!isset($config['arguments'])) { | |||
$config['arguments'] = []; | |||
} | |||
// instantiate object store implementation | |||
$name = $config['class']; | |||
if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) { | |||
$segments = explode('\\', $name); | |||
OC_App::loadApp(strtolower($segments[1])); | |||
} | |||
$config['arguments']['objectstore'] = new $config['class']($config['arguments']); | |||
// mount with plain / root object store implementation | |||
$config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage'; | |||
// mount object storage as root | |||
\OC\Files\Filesystem::initMountManager(); | |||
if (!self::$rootMounted) { | |||
\OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/'); | |||
self::$rootMounted = true; | |||
} | |||
} | |||
/** | |||
* mounting an object storage as the root fs will in essence remove the | |||
* necessity of a data folder being present. | |||
* | |||
* @param array $config containing 'class' and optional 'arguments' | |||
* @suppress PhanDeprecatedFunction | |||
*/ | |||
private static function initObjectStoreMultibucketRootFS($config) { | |||
// check misconfiguration | |||
if (empty($config['class'])) { | |||
\OCP\Util::writeLog('files', 'No class given for objectstore', ILogger::ERROR); | |||
} | |||
if (!isset($config['arguments'])) { | |||
$config['arguments'] = []; | |||
} | |||
// instantiate object store implementation | |||
$name = $config['class']; | |||
if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) { | |||
$segments = explode('\\', $name); | |||
OC_App::loadApp(strtolower($segments[1])); | |||
} | |||
if (!isset($config['arguments']['bucket'])) { | |||
$config['arguments']['bucket'] = ''; | |||
} | |||
// put the root FS always in first bucket for multibucket configuration | |||
$config['arguments']['bucket'] .= '0'; | |||
$config['arguments']['objectstore'] = new $config['class']($config['arguments']); | |||
// mount with plain / root object store implementation | |||
$config['class'] = '\OC\Files\ObjectStore\ObjectStoreStorage'; | |||
// mount object storage as root | |||
\OC\Files\Filesystem::initMountManager(); | |||
if (!self::$rootMounted) { | |||
\OC\Files\Filesystem::mount($config['class'], $config['arguments'], '/'); | |||
self::$rootMounted = true; | |||
} | |||
} | |||
/** | |||
* Can be set up | |||
* | |||
@@ -279,19 +189,6 @@ class OC_Util { | |||
\OC\Files\Filesystem::logWarningWhenAddingStorageWrapper($prevLogging); | |||
//check if we are using an object storage | |||
$objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null); | |||
$objectStoreMultibucket = \OC::$server->getSystemConfig()->getValue('objectstore_multibucket', null); | |||
// use the same order as in ObjectHomeMountProvider | |||
if (isset($objectStoreMultibucket)) { | |||
self::initObjectStoreMultibucketRootFS($objectStoreMultibucket); | |||
} elseif (isset($objectStore)) { | |||
self::initObjectStoreRootFS($objectStore); | |||
} else { | |||
self::initLocalStorageRootFS(); | |||
} | |||
/** @var \OCP\Files\Config\IMountProviderCollection $mountProviderCollection */ | |||
$mountProviderCollection = \OC::$server->query(\OCP\Files\Config\IMountProviderCollection::class); | |||
$rootMountProviders = $mountProviderCollection->getRootMounts(); | |||
@@ -501,7 +398,6 @@ class OC_Util { | |||
\OC::$server->getRootFolder()->clearCache(); | |||
self::$fsSetup = false; | |||
self::$rootFsSetup = false; | |||
self::$rootMounted = false; | |||
} | |||
/** |
@@ -0,0 +1,141 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2022 Robin Appelman <robin@icewind.nl> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
* | |||
*/ | |||
namespace Test\Files\Mount; | |||
use OC\Files\Mount\RootMountProvider; | |||
use OC\Files\ObjectStore\ObjectStoreStorage; | |||
use OC\Files\ObjectStore\S3; | |||
use OC\Files\Storage\LocalRootStorage; | |||
use OC\Files\Storage\StorageFactory; | |||
use OCP\IConfig; | |||
use Psr\Log\LoggerInterface; | |||
use Test\TestCase; | |||
/** | |||
* @group DB | |||
*/ | |||
class RootMountProviderTest extends TestCase { | |||
private StorageFactory $loader; | |||
protected function setUp(): void { | |||
parent::setUp(); | |||
$this->loader = new StorageFactory(); | |||
} | |||
private function getConfig(array $systemConfig): IConfig { | |||
$config = $this->createMock(IConfig::class); | |||
$config->method('getSystemValue') | |||
->willReturnCallback(function (string $key, $default) use ($systemConfig) { | |||
return $systemConfig[$key] ?? $default; | |||
}); | |||
return $config; | |||
} | |||
private function getProvider(array $systemConfig): RootMountProvider { | |||
$config = $this->getConfig($systemConfig); | |||
$provider = new RootMountProvider($config, $this->createMock(LoggerInterface::class)); | |||
return $provider; | |||
} | |||
public function testLocal() { | |||
$provider = $this->getProvider([ | |||
'datadirectory' => '/data', | |||
]); | |||
$mounts = $provider->getRootMounts($this->loader); | |||
$this->assertCount(1, $mounts); | |||
$mount = $mounts[0]; | |||
$this->assertEquals('/', $mount->getMountPoint()); | |||
/** @var LocalRootStorage $storage */ | |||
$storage = $mount->getStorage(); | |||
$this->assertInstanceOf(LocalRootStorage::class, $storage); | |||
$this->assertEquals('/data/', $storage->getSourcePath('')); | |||
} | |||
public function testObjectStore() { | |||
$provider = $this->getProvider([ | |||
'objectstore' => [ | |||
"class" => "OC\Files\ObjectStore\S3", | |||
"arguments" => [ | |||
"bucket" => "nextcloud", | |||
"autocreate" => true, | |||
"key" => "minio", | |||
"secret" => "minio123", | |||
"hostname" => "localhost", | |||
"port" => 9000, | |||
"use_ssl" => false, | |||
"use_path_style" => true, | |||
"uploadPartSize" => 52428800, | |||
], | |||
], | |||
]); | |||
$mounts = $provider->getRootMounts($this->loader); | |||
$this->assertCount(1, $mounts); | |||
$mount = $mounts[0]; | |||
$this->assertEquals('/', $mount->getMountPoint()); | |||
/** @var ObjectStoreStorage $storage */ | |||
$storage = $mount->getStorage(); | |||
$this->assertInstanceOf(ObjectStoreStorage::class, $storage); | |||
$class = new \ReflectionClass($storage); | |||
$prop = $class->getProperty('objectStore'); | |||
$prop->setAccessible(true); | |||
/** @var S3 $objectStore */ | |||
$objectStore = $prop->getValue($storage); | |||
$this->assertEquals('nextcloud', $objectStore->getBucket()); | |||
} | |||
public function testObjectStoreMultiBucket() { | |||
$provider = $this->getProvider([ | |||
'objectstore_multibucket' => [ | |||
"class" => "OC\Files\ObjectStore\S3", | |||
"arguments" => [ | |||
"bucket" => "nextcloud", | |||
"autocreate" => true, | |||
"key" => "minio", | |||
"secret" => "minio123", | |||
"hostname" => "localhost", | |||
"port" => 9000, | |||
"use_ssl" => false, | |||
"use_path_style" => true, | |||
"uploadPartSize" => 52428800, | |||
], | |||
], | |||
]); | |||
$mounts = $provider->getRootMounts($this->loader); | |||
$this->assertCount(1, $mounts); | |||
$mount = $mounts[0]; | |||
$this->assertEquals('/', $mount->getMountPoint()); | |||
/** @var ObjectStoreStorage $storage */ | |||
$storage = $mount->getStorage(); | |||
$this->assertInstanceOf(ObjectStoreStorage::class, $storage); | |||
$class = new \ReflectionClass($storage); | |||
$prop = $class->getProperty('objectStore'); | |||
$prop->setAccessible(true); | |||
/** @var S3 $objectStore */ | |||
$objectStore = $prop->getValue($storage); | |||
$this->assertEquals('nextcloud0', $objectStore->getBucket()); | |||
} | |||
} |