diff --git a/.github/workflows/phpunit-sqlite-s3.yml b/.github/workflows/phpunit-sqlite-s3.yml new file mode 100644 index 000000000..d14e36755 --- /dev/null +++ b/.github/workflows/phpunit-sqlite-s3.yml @@ -0,0 +1,210 @@ +# This workflow is provided via the organization template repository +# +# https://github.com/nextcloud/.github +# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization +# +# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors +# SPDX-License-Identifier: MIT + +name: PHPUnit SQLite - S3 + +on: pull_request + +permissions: + contents: read + +concurrency: + group: phpunit-sqlite-s3-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + matrix: + runs-on: ubuntu-latest-low + outputs: + php-version: ${{ steps.versions.outputs.php-available-list }} + server-max: ${{ steps.versions.outputs.branches-max-list }} + steps: + - name: Checkout app + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - name: Get version matrix + id: versions + uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1 + + changes: + runs-on: ubuntu-latest-low + permissions: + contents: read + pull-requests: read + + outputs: + src: ${{ steps.changes.outputs.src}} + + steps: + - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: changes + continue-on-error: true + with: + filters: | + src: + - '.github/workflows/**' + - 'appinfo/**' + - 'lib/**' + - 'templates/**' + - 'tests/**' + - 'vendor/**' + - 'vendor-bin/**' + - '.php-cs-fixer.dist.php' + - 'composer.json' + - 'composer.lock' + + phpunit-sqlite: + runs-on: ubuntu-latest + + needs: [changes, matrix] + if: needs.changes.outputs.src != 'false' + + strategy: + matrix: + php-versions: ${{ fromJson(needs.matrix.outputs.php-version) }} + server-versions: ${{ fromJson(needs.matrix.outputs.server-max) }} + + name: SQLite PHP S3 ${{ matrix.php-versions }} Nextcloud ${{ matrix.server-versions }} + + services: + minio: + image: bitnami/minio@sha256:50cec18ac4184af4671a78aedd5554942c8ae105d51a465fa82037949046da01 # v2025.4.22 + env: + MINIO_ROOT_USER: nextcloud + MINIO_ROOT_PASSWORD: bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ= + MINIO_DEFAULT_BUCKETS: nextcloud + ports: + - "9000:9000" + + steps: + - name: Set app env + if: ${{ env.APP_NAME == '' }} + run: | + # Split and keep last + echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV + + - name: Checkout server + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + submodules: true + repository: nextcloud/server + ref: ${{ matrix.server-versions }} + + - name: Checkout Circles + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + repository: nextcloud/circles + ref: master + path: apps/circles + + - name: Checkout app + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + path: apps/${{ env.APP_NAME }} + + - name: Set up php ${{ matrix.php-versions }} + uses: shivammathur/setup-php@0f7f1d08e3e32076e51cae65eb0b0c871405b16e # v2.34.1 + with: + php-version: ${{ matrix.php-versions }} + # https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation + extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite + coverage: none + ini-file: development + # Temporary workaround for missing pcntl_* in PHP 8.3 + ini-values: disable_functions= + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check composer file existence + id: check_composer + uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0 + with: + files: apps/${{ env.APP_NAME }}/composer.json + + - name: Set up Circles dependencies + working-directory: apps/circles + run: composer i + + - name: Set up dependencies + # Only run if phpunit config file exists + if: steps.check_composer.outputs.files_exists == 'true' + working-directory: apps/${{ env.APP_NAME }} + run: | + composer remove nextcloud/ocp --dev --no-scripts + composer i + + - name: Set up Nextcloud + env: + DB_PORT: 4444 + run: | + mkdir data + echo ' ["class" => "OC\Files\ObjectStore\S3", "arguments" => ["bucket" => "nextcloud", "autocreate" => true, "key" => "nextcloud", "secret" => "bWluaW8tc2VjcmV0LWtleS1uZXh0Y2xvdWQ=", "hostname" => "localhost", "port" => 9000, "use_ssl" => false, "use_path_style" => true, "uploadPartSize" => 52428800]]];' > config/config.php + ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin + ./occ app:enable --force circles + ./occ app:enable --force ${{ env.APP_NAME }} + + - name: Check PHPUnit script is defined + id: check_phpunit + continue-on-error: true + working-directory: apps/${{ env.APP_NAME }} + run: | + composer run --list | grep '^ test:unit ' | wc -l | grep 1 + + - name: PHPUnit + # Only run if phpunit config file exists + if: steps.check_phpunit.outcome == 'success' + working-directory: apps/${{ env.APP_NAME }} + run: composer run test:unit + + - name: Check PHPUnit integration script is defined + id: check_integration + continue-on-error: true + working-directory: apps/${{ env.APP_NAME }} + run: | + composer run --list | grep '^ test:integration ' | wc -l | grep 1 + + - name: Run Nextcloud + # Only run if phpunit integration config file exists + if: steps.check_integration.outcome == 'success' + run: php -S localhost:8080 & + + - name: PHPUnit integration + # Only run if phpunit integration config file exists + if: steps.check_integration.outcome == 'success' + working-directory: apps/${{ env.APP_NAME }} + run: composer run test:integration + + - name: Print logs + if: always() + run: | + cat data/nextcloud.log + + - name: Skipped + # Fail the action when neither unit nor integration tests ran + if: steps.check_phpunit.outcome == 'failure' && steps.check_integration.outcome == 'failure' + run: | + echo 'Neither PHPUnit nor PHPUnit integration tests are specified in composer.json scripts' + exit 1 + + summary: + permissions: + contents: none + runs-on: ubuntu-latest-low + needs: [changes, phpunit-sqlite] + + if: always() + + name: phpunit-sqlite-summary + + steps: + - name: Summary status + run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-sqlite.result != 'success' }}; then exit 1; fi diff --git a/lib/Folder/FolderDefinition.php b/lib/Folder/FolderDefinition.php index 40cebd420..5c91d9ab7 100644 --- a/lib/Folder/FolderDefinition.php +++ b/lib/Folder/FolderDefinition.php @@ -16,6 +16,11 @@ public function __construct( public readonly bool $acl, public readonly int $storageId, public readonly int $rootId, + public readonly array $options, ) { } + + public function useSeparateStorage(): bool { + return $this->options['separate-storage'] ?? false; + } } diff --git a/lib/Folder/FolderDefinitionWithMappings.php b/lib/Folder/FolderDefinitionWithMappings.php index b2d0f5b8d..88a903b33 100644 --- a/lib/Folder/FolderDefinitionWithMappings.php +++ b/lib/Folder/FolderDefinitionWithMappings.php @@ -26,10 +26,11 @@ public function __construct( bool $acl, int $storageId, int $rootId, + array $options, public readonly array $groups, public readonly array $manage, ) { - parent::__construct($id, $mountPoint, $quota, $acl, $storageId, $rootId); + parent::__construct($id, $mountPoint, $quota, $acl, $storageId, $rootId, $options); } /** @@ -44,6 +45,7 @@ public static function fromFolder(FolderDefinition $folder, array $groups, array $folder->acl, $folder->storageId, $folder->rootId, + $folder->options, $groups, $manage, ); diff --git a/lib/Folder/FolderDefinitionWithPermissions.php b/lib/Folder/FolderDefinitionWithPermissions.php index 4b46a2aa5..086bf9cb9 100644 --- a/lib/Folder/FolderDefinitionWithPermissions.php +++ b/lib/Folder/FolderDefinitionWithPermissions.php @@ -27,10 +27,11 @@ public function __construct( bool $acl, int $storageId, int $rootId, + array $options, public readonly ICacheEntry $rootCacheEntry, public readonly int $permissions, ) { - parent::__construct($id, $mountPoint, $quota, $acl, $storageId, $rootId); + parent::__construct($id, $mountPoint, $quota, $acl, $storageId, $rootId, $options); } /** @@ -45,6 +46,7 @@ public static function fromFolder(FolderDefinition $folder, ICacheEntry $rootCac $folder->acl, $folder->storageId, $folder->rootId, + $folder->options, $rootCacheEntry, $permissions, ); @@ -71,6 +73,7 @@ public function withAddedPermissions(int $permissions): self { $this->acl, $this->storageId, $this->rootId, + $this->options, $this->rootCacheEntry, $this->permissions | $permissions, ); diff --git a/lib/Folder/FolderManager.php b/lib/Folder/FolderManager.php index 9b027a449..3335b70c9 100644 --- a/lib/Folder/FolderManager.php +++ b/lib/Folder/FolderManager.php @@ -77,7 +77,7 @@ public function getAllFolders(): array { $query = $this->connection->getQueryBuilder(); - $query->select('folder_id', 'mount_point', 'quota', 'acl', 'storage_id', 'root_id') + $query->select('folder_id', 'mount_point', 'quota', 'acl', 'storage_id', 'root_id', 'options') ->from('group_folders', 'f'); $rows = $query->executeQuery()->fetchAll(); @@ -108,6 +108,7 @@ private function selectWithFileCache(?IQueryBuilder $query = null): IQueryBuilde 'acl', 'storage_id', 'root_id', + 'options', 'c.fileid', 'c.storage', 'c.path', @@ -549,6 +550,7 @@ public function searchUsers(int $id, string $search = '', int $limit = 10, int $ } private function rowToFolder(array $row): FolderDefinition { + $options = json_decode($row['options'], true); return new FolderDefinition( (int)$row['folder_id'], (string)$row['mount_point'], @@ -556,6 +558,7 @@ private function rowToFolder(array $row): FolderDefinition { (bool)$row['acl'], (int)$row['storage_id'], (int)$row['root_id'], + is_array($options) ? $options : [], ); } @@ -660,11 +663,14 @@ public function createFolder(string $mountPoint): int { ->values([ 'mount_point' => $query->createNamedParameter($mountPoint), 'quota' => self::SPACE_DEFAULT, + 'options' => $query->createNamedParameter(json_encode([ + 'separate-storage' => true, + ])) ]); $query->executeStatement(); $id = $query->getLastInsertId(); - ['storage_id' => $storageId, 'root_id' => $rootId] = $this->folderStorageManager->getRootAndStorageIdForFolder($id); + ['storage_id' => $storageId, 'root_id' => $rootId] = $this->folderStorageManager->initRootAndStorageForFolder($id, true); $query->update('group_folders') ->set('root_id', $query->createNamedParameter($rootId)) ->set('storage_id', $query->createNamedParameter($storageId)) diff --git a/lib/Folder/FolderWithMappingsAndCache.php b/lib/Folder/FolderWithMappingsAndCache.php index 4ddf3d4fe..b28936af4 100644 --- a/lib/Folder/FolderWithMappingsAndCache.php +++ b/lib/Folder/FolderWithMappingsAndCache.php @@ -27,11 +27,12 @@ public function __construct( bool $acl, int $storageId, int $rootId, + array $options, array $groups, array $manage, public readonly ICacheEntry $rootCacheEntry, ) { - parent::__construct($id, $mountPoint, $quota, $acl, $storageId, $rootId, $groups, $manage); + parent::__construct($id, $mountPoint, $quota, $acl, $storageId, $rootId, $options, $groups, $manage); } /** @@ -46,6 +47,7 @@ public static function fromFolderWithMapping(FolderDefinitionWithMappings $folde $folder->acl, $folder->storageId, $folder->rootId, + $folder->options, $folder->groups, $folder->manage, $rootCacheEntry, diff --git a/lib/Mount/FolderStorageManager.php b/lib/Mount/FolderStorageManager.php index 54ac8fde7..94b426cb0 100644 --- a/lib/Mount/FolderStorageManager.php +++ b/lib/Mount/FolderStorageManager.php @@ -9,15 +9,20 @@ namespace OCA\GroupFolders\Mount; use OC\Files\Cache\Cache; +use OC\Files\ObjectStore\ObjectStoreStorage; +use OC\Files\ObjectStore\PrimaryObjectStoreConfig; +use OC\Files\Storage\Local; use OC\Files\Storage\Wrapper\Jail; use OCA\GroupFolders\ACL\ACLManagerFactory; use OCA\GroupFolders\ACL\ACLStorageWrapper; +use OCA\GroupFolders\AppInfo\Application; use OCA\GroupFolders\Folder\FolderDefinition; use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorage; use OCP\IAppConfig; +use OCP\IConfig; use OCP\IUser; class FolderStorageManager { @@ -27,15 +32,17 @@ public function __construct( private readonly IRootFolder $rootFolder, private readonly IAppConfig $appConfig, private readonly ACLManagerFactory $aclManagerFactory, + private readonly IConfig $config, + private readonly PrimaryObjectStoreConfig $primaryObjectStoreConfig, ) { - $this->enableEncryption = $this->appConfig->getValueString('groupfolders', 'enable_encryption', 'false') === 'true'; + $this->enableEncryption = $this->appConfig->getValueString(Application::APP_ID, 'enable_encryption', 'false') === 'true'; } /** * @return array{storage_id: int, root_id: int} */ - public function getRootAndStorageIdForFolder(int $folderId): array { - $storage = $this->getBaseStorageForFolder($folderId); + public function initRootAndStorageForFolder(int $folderId, bool $separateStorage): array { + $storage = $this->getBaseStorageForFolder($folderId, $separateStorage, init: true); $cache = $storage->getCache(); $id = $cache->getId(''); if ($id === -1) { @@ -54,7 +61,128 @@ public function getRootAndStorageIdForFolder(int $folderId): array { /** * @param 'files'|'trash'|'versions' $type */ - public function getBaseStorageForFolder(int $folderId, ?FolderDefinition $folder = null, ?IUser $user = null, bool $inShare = false, string $type = 'files'): IStorage { + public function getBaseStorageForFolder( + int $folderId, + bool $separateStorage, + ?FolderDefinition $folder = null, + ?IUser $user = null, + bool $inShare = false, + string $type = 'files', + bool $init = false, + ): IStorage { + if ($separateStorage) { + return $this->getBaseStorageForFolderSeparate($folderId, $folder, $user, $inShare, $type, $init); + } else { + return $this->getBaseStorageForFolderRootJail($folderId, $folder, $user, $inShare, $type); + } + } + + /** + * @param 'files'|'trash'|'versions' $type + */ + private function getBaseStorageForFolderSeparate( + int $folderId, + ?FolderDefinition $folder = null, + ?IUser $user = null, + bool $inShare = false, + string $type = 'files', + bool $init = false, + ): IStorage { + if ($this->primaryObjectStoreConfig->hasObjectStore()) { + $storage = $this->getBaseStorageForFolderSeparateStorageObject($folderId, $init); + } else { + $storage = $this->getBaseStorageForFolderSeparateStorageLocal($folderId, $init); + } + + if ($folder?->acl && $user) { + $aclManager = $this->aclManagerFactory->getACLManager($user); + $storage = new ACLStorageWrapper([ + 'storage' => $storage, + 'acl_manager' => $aclManager, + 'in_share' => $inShare, + 'storage_id' => $storage->getCache()->getNumericStorageId(), + ]); + } + + if ($this->enableEncryption) { + return new GroupFolderEncryptionJail([ + 'storage' => $storage, + 'root' => $type, + ]); + } else { + return new Jail([ + 'storage' => $storage, + 'root' => $type, + ]); + } + } + + private function getBaseStorageForFolderSeparateStorageLocal( + int $folderId, + bool $init = false, + ): IStorage { + $dataDirectory = $this->config->getSystemValue('datadirectory'); + $rootPath = $dataDirectory . '/__groupfolders/' . $folderId; + if ($init) { + $result = mkdir($rootPath . '/files', recursive: true); + $result = $result && mkdir($rootPath . '/trash'); + $result = $result && mkdir($rootPath . '/versions'); + + if (!$result) { + throw new \Exception('Failed to create base directories for group folder ' . $folderId); + } + } + + $storage = new Local([ + 'datadir' => $rootPath, + ]); + + if ($init) { + $storage->getScanner()->scan(''); + } + return $storage; + } + + private function getBaseStorageForFolderSeparateStorageObject( + int $folderId, + bool $init = false, + ): IStorage { + $objectStoreConfig = $this->primaryObjectStoreConfig->getObjectStoreConfiguration($this->getObjectStorageKey($folderId)); + + if ($objectStoreConfig['arguments']['multibucket']) { + $objectStoreConfig['arguments']['bucket'] = $this->getObjectStorageBucket($folderId, $objectStoreConfig); + } + + $objectStore = $this->primaryObjectStoreConfig->buildObjectStore($objectStoreConfig); + $arguments = array_merge($objectStoreConfig['arguments'], [ + 'objectstore' => $objectStore, + ]); + $arguments['storageid'] = 'object::groupfolder:' . $folderId . '.' . $objectStore->getStorageId(); + + $storage = new ObjectStoreStorage($arguments); + + if ($init) { + $result = $storage->mkdir('files'); + $result = $result && $storage->mkdir('trash'); + $result = $result && $storage->mkdir('versions'); + + if (!$result) { + throw new \Exception('Failed to create base directories for group folder ' . $folderId); + } + } + return $storage; + } + + /** + * @param 'files'|'trash'|'versions' $type + */ + private function getBaseStorageForFolderRootJail( + int $folderId, + ?FolderDefinition $folder = null, + ?IUser $user = null, + bool $inShare = false, + string $type = 'files', + ): IStorage { try { /** @var Folder $parentFolder */ $parentFolder = $this->rootFolder->get('__groupfolders'); @@ -106,11 +234,51 @@ public function getBaseStorageForFolder(int $folderId, ?FolderDefinition $folder public function deleteStoragesForFolder(FolderDefinition $folder): void { foreach (['files', 'trash', 'versions'] as $type) { - $storage = $this->getBaseStorageForFolder($folder->id, $folder, null, false, $type); + $storage = $this->getBaseStorageForFolder($folder->id, $folder->useSeparateStorage(), $folder, null, false, $type); /** @var Cache $cache */ $cache = $storage->getCache(); $cache->clear(); $storage->rmdir(''); } } + + private function getObjectStorageKey(int $folderId): string { + $configs = $this->primaryObjectStoreConfig->getObjectStoreConfigs(); + if ($this->primaryObjectStoreConfig->hasMultipleObjectStorages()) { + $configKey = 'object_store_key_' . $folderId; + $storageConfigKey = $this->appConfig->getValueString(Application::APP_ID, $configKey); + if (!$storageConfigKey) { + $storageConfigKey = isset($configs['groupfolders']) ? $this->primaryObjectStoreConfig->resolveAlias('groupfolders') : $this->primaryObjectStoreConfig->resolveAlias('default'); + $this->appConfig->setValueString(Application::APP_ID, $configKey, $storageConfigKey); + } + return $storageConfigKey; + } else { + return 'default'; + } + } + + private function getObjectStorageBucket(int $folderId, array $objectStoreConfig): string { + $bucketKey = 'object_store_bucket_' . $folderId; + $bucket = $this->appConfig->getValueString(Application::APP_ID, $bucketKey); + if (!$bucket) { + $bucketBase = $objectStoreConfig['arguments']['bucket'] ?? ''; + $bucket = $bucketBase . $this->calculateBucketNum((string)$folderId, $objectStoreConfig); + + $this->appConfig->setValueString(Application::APP_ID, $bucketKey, $bucket); + } + return $bucket; + } + + // logic taken from OC\Files\ObjectStore\Mapper which we can't use because it requires an IUser + private function calculateBucketNum(string $key, array $objectStoreConfig): string { + $numBuckets = $objectStoreConfig['arguments']['num_buckets'] ?? 64; + + // Get the bucket config and shift if provided. + // Allow us to prevent writing in old filled buckets + $minBucket = (int)($objectStoreConfig['arguments']['min_bucket'] ?? 0); + + $hash = md5($key); + $num = hexdec(substr($hash, 0, 4)); + return (string)(($num % ($numBuckets - $minBucket)) + $minBucket); + } } diff --git a/lib/Mount/MountProvider.php b/lib/Mount/MountProvider.php index 5c3b831df..e4057e499 100644 --- a/lib/Mount/MountProvider.php +++ b/lib/Mount/MountProvider.php @@ -160,7 +160,7 @@ public function getTrashMount( ?ICacheEntry $cacheEntry = null, ): IMountPoint { - $storage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder, null, false, 'trash'); + $storage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder->useSeparateStorage(), $folder, null, false, 'trash'); if ($user) { $storage->setOwner($user->getUID()); @@ -184,7 +184,7 @@ public function getVersionsMount( ?ICacheEntry $cacheEntry = null, ): IMountPoint { if (!$cacheEntry) { - $storage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder, null, false, 'versions'); + $storage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder->useSeparateStorage(), $folder, null, false, 'versions'); $cacheEntry = $storage->getCache()->get(''); if (!$cacheEntry) { $storage->getScanner()->scan(''); @@ -221,9 +221,9 @@ public function getGroupFolderStorage( ): IStorage { if ($user) { $inShare = !\OC::$CLI && ($this->getCurrentUID() === null || $this->getCurrentUID() !== $user->getUID()); - $baseStorage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder, $user, $inShare, $type); + $baseStorage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder->useSeparateStorage(), $folder, $user, $inShare, $type); } else { - $baseStorage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder, null, false, $type); + $baseStorage = $this->folderStorageManager->getBaseStorageForFolder($folder->id, $folder->useSeparateStorage(), $folder, null, false, $type); } if ($user) { diff --git a/psalm.xml b/psalm.xml index 76ed29172..9548f216e 100644 --- a/psalm.xml +++ b/psalm.xml @@ -38,6 +38,7 @@ + diff --git a/tests/Folder/FolderManagerTest.php b/tests/Folder/FolderManagerTest.php index ba956896a..6d8ba943d 100644 --- a/tests/Folder/FolderManagerTest.php +++ b/tests/Folder/FolderManagerTest.php @@ -376,6 +376,7 @@ public function testGetFoldersForUserSimple(): void { false, 1, 2, + [], $cacheEntry, 31, ); @@ -404,6 +405,7 @@ public function testGetFoldersForUserMerge(): void { false, 1, 2, + [], $cacheEntry, 3, ); @@ -414,6 +416,7 @@ public function testGetFoldersForUserMerge(): void { false, 1, 2, + [], $cacheEntry, 8, ); @@ -424,6 +427,7 @@ public function testGetFoldersForUserMerge(): void { false, 1, 2, + [], $cacheEntry, 8 + 3, ); @@ -452,6 +456,7 @@ public function testGetFolderPermissionsForUserMerge(): void { false, 1, 2, + [], $cacheEntry, 3, ); @@ -462,6 +467,7 @@ public function testGetFolderPermissionsForUserMerge(): void { false, 1, 2, + [], $cacheEntry, 8, ); diff --git a/tests/stubs/oc_files_objectstore_primaryobjectstoreconfig.php b/tests/stubs/oc_files_objectstore_primaryobjectstoreconfig.php new file mode 100644 index 000000000..a854c5955 --- /dev/null +++ b/tests/stubs/oc_files_objectstore_primaryobjectstoreconfig.php @@ -0,0 +1,67 @@ +, arguments: array{multibucket: bool, ...}} + */ +class PrimaryObjectStoreConfig +{ + public function __construct(private readonly \OCP\IConfig $config, private readonly \OCP\App\IAppManager $appManager) + { + } + /** + * @param ObjectStoreConfig $config + */ + public function buildObjectStore(array $config): \OCP\Files\ObjectStore\IObjectStore + { + } + /** + * @return ?ObjectStoreConfig + */ + public function getObjectStoreConfigForRoot(): ?array + { + } + /** + * @return ?ObjectStoreConfig + */ + public function getObjectStoreConfigForUser(\OCP\IUser $user): ?array + { + } + /** + * @param string $name + * @return ObjectStoreConfig + */ + public function getObjectStoreConfiguration(string $name): array + { + } + public function resolveAlias(string $name): string + { + } + public function hasObjectStore(): bool + { + } + public function hasMultipleObjectStorages(): bool + { + } + /** + * @return ?array + * @throws InvalidObjectStoreConfigurationException + */ + public function getObjectStoreConfigs(): ?array + { + } + public function getBucketForUser(\OCP\IUser $user, array $config): string + { + } + public function getSetBucketForUser(\OCP\IUser $user): ?string + { + } + public function getObjectStoreForUser(\OCP\IUser $user): string + { + } +}