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
+ {
+ }
+}