Completed
Push — master ( 8c90d4...be8b2b )
by
unknown
26:02
created
lib/private/Files/SetupManager.php 2 patches
Indentation   +675 added lines, -675 removed lines patch added patch discarded remove patch
@@ -62,681 +62,681 @@
 block discarded – undo
62 62
 use function in_array;
63 63
 
64 64
 class SetupManager {
65
-	private bool $rootSetup = false;
66
-	// List of users for which at least one mount is setup
67
-	private array $setupUsers = [];
68
-	// List of users for which all mounts are setup
69
-	private array $setupUsersComplete = [];
70
-	/**
71
-	 * An array of provider classes that have been set up, indexed by UserUID.
72
-	 *
73
-	 * @var array<string, class-string<IMountProvider>[]>
74
-	 */
75
-	private array $setupUserMountProviders = [];
76
-	/**
77
-	 * An array of paths that have already been set up
78
-	 *
79
-	 * @var array<string, int>
80
-	 */
81
-	private array $setupMountProviderPaths = [];
82
-	private ICache $cache;
83
-	private bool $listeningForProviders;
84
-	private array $fullSetupRequired = [];
85
-	private bool $setupBuiltinWrappersDone = false;
86
-	private bool $forceFullSetup = false;
87
-	private const SETUP_WITH_CHILDREN = 1;
88
-	private const SETUP_WITHOUT_CHILDREN = 0;
89
-
90
-	public function __construct(
91
-		private IEventLogger $eventLogger,
92
-		private MountProviderCollection $mountProviderCollection,
93
-		private IMountManager $mountManager,
94
-		private IUserManager $userManager,
95
-		private IEventDispatcher $eventDispatcher,
96
-		private IUserMountCache $userMountCache,
97
-		private ILockdownManager $lockdownManager,
98
-		private IUserSession $userSession,
99
-		ICacheFactory $cacheFactory,
100
-		private LoggerInterface $logger,
101
-		private IConfig $config,
102
-		private ShareDisableChecker $shareDisableChecker,
103
-		private IAppManager $appManager,
104
-		private FileAccess $fileAccess,
105
-	) {
106
-		$this->cache = $cacheFactory->createDistributed('setupmanager::');
107
-		$this->listeningForProviders = false;
108
-		$this->forceFullSetup = $this->config->getSystemValueBool('debug.force-full-fs-setup');
109
-
110
-		$this->setupListeners();
111
-	}
112
-
113
-	private function isSetupStarted(IUser $user): bool {
114
-		return in_array($user->getUID(), $this->setupUsers, true);
115
-	}
116
-
117
-	public function isSetupComplete(IUser $user): bool {
118
-		return in_array($user->getUID(), $this->setupUsersComplete, true);
119
-	}
120
-
121
-	/**
122
-	 * Checks if a path has been cached either directly or through a full setup
123
-	 * of one of its parents.
124
-	 */
125
-	private function isPathSetup(string $path): bool {
126
-		// if the exact path was already setup with or without children
127
-		if (array_key_exists($path, $this->setupMountProviderPaths)) {
128
-			return true;
129
-		}
130
-
131
-		// or if any of the ancestors was fully setup
132
-		while (($path = dirname($path)) !== '/') {
133
-			$setupPath = $this->setupMountProviderPaths[$path . '/'] ?? null;
134
-			if ($setupPath === self::SETUP_WITH_CHILDREN) {
135
-				return true;
136
-			}
137
-		}
138
-
139
-		return false;
140
-	}
141
-
142
-	private function setupBuiltinWrappers() {
143
-		if ($this->setupBuiltinWrappersDone) {
144
-			return;
145
-		}
146
-		$this->setupBuiltinWrappersDone = true;
147
-
148
-		// load all filesystem apps before, so no setup-hook gets lost
149
-		$this->appManager->loadApps(['filesystem']);
150
-		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
151
-
152
-		Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
153
-			if ($storage->instanceOfStorage(Common::class)) {
154
-				$options = array_merge($mount->getOptions(), ['mount_point' => $mountPoint]);
155
-				$storage->setMountOptions($options);
156
-			}
157
-			return $storage;
158
-		});
159
-
160
-		$reSharingEnabled = Share::isResharingAllowed();
161
-		$user = $this->userSession->getUser();
162
-		$sharingEnabledForUser = $user ? !$this->shareDisableChecker->sharingDisabledForUser($user->getUID()) : true;
163
-		Filesystem::addStorageWrapper(
164
-			'sharing_mask',
165
-			function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
166
-				$sharingEnabledForMount = $mount->getOption('enable_sharing', true);
167
-				$isShared = $mount instanceof ISharedMountPoint;
168
-				if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
169
-					return new PermissionsMask([
170
-						'storage' => $storage,
171
-						'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
172
-					]);
173
-				}
174
-				return $storage;
175
-			}
176
-		);
177
-
178
-		// install storage availability wrapper, before most other wrappers
179
-		Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
180
-			$externalMount = $mount instanceof ExternalMountPoint || $mount instanceof Mount;
181
-			if ($externalMount && !$storage->isLocal()) {
182
-				return new Availability(['storage' => $storage]);
183
-			}
184
-			return $storage;
185
-		});
186
-
187
-		Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
188
-			if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) {
189
-				return new Encoding(['storage' => $storage]);
190
-			}
191
-			return $storage;
192
-		});
193
-
194
-		$quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false);
195
-		Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
196
-			// set up quota for home storages, even for other users
197
-			// which can happen when using sharing
198
-			if ($mount instanceof HomeMountPoint) {
199
-				$user = $mount->getUser();
200
-				return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
201
-					return $user->getQuotaBytes();
202
-				}, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
203
-			}
204
-
205
-			return $storage;
206
-		});
207
-
208
-		Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
209
-			/*
65
+    private bool $rootSetup = false;
66
+    // List of users for which at least one mount is setup
67
+    private array $setupUsers = [];
68
+    // List of users for which all mounts are setup
69
+    private array $setupUsersComplete = [];
70
+    /**
71
+     * An array of provider classes that have been set up, indexed by UserUID.
72
+     *
73
+     * @var array<string, class-string<IMountProvider>[]>
74
+     */
75
+    private array $setupUserMountProviders = [];
76
+    /**
77
+     * An array of paths that have already been set up
78
+     *
79
+     * @var array<string, int>
80
+     */
81
+    private array $setupMountProviderPaths = [];
82
+    private ICache $cache;
83
+    private bool $listeningForProviders;
84
+    private array $fullSetupRequired = [];
85
+    private bool $setupBuiltinWrappersDone = false;
86
+    private bool $forceFullSetup = false;
87
+    private const SETUP_WITH_CHILDREN = 1;
88
+    private const SETUP_WITHOUT_CHILDREN = 0;
89
+
90
+    public function __construct(
91
+        private IEventLogger $eventLogger,
92
+        private MountProviderCollection $mountProviderCollection,
93
+        private IMountManager $mountManager,
94
+        private IUserManager $userManager,
95
+        private IEventDispatcher $eventDispatcher,
96
+        private IUserMountCache $userMountCache,
97
+        private ILockdownManager $lockdownManager,
98
+        private IUserSession $userSession,
99
+        ICacheFactory $cacheFactory,
100
+        private LoggerInterface $logger,
101
+        private IConfig $config,
102
+        private ShareDisableChecker $shareDisableChecker,
103
+        private IAppManager $appManager,
104
+        private FileAccess $fileAccess,
105
+    ) {
106
+        $this->cache = $cacheFactory->createDistributed('setupmanager::');
107
+        $this->listeningForProviders = false;
108
+        $this->forceFullSetup = $this->config->getSystemValueBool('debug.force-full-fs-setup');
109
+
110
+        $this->setupListeners();
111
+    }
112
+
113
+    private function isSetupStarted(IUser $user): bool {
114
+        return in_array($user->getUID(), $this->setupUsers, true);
115
+    }
116
+
117
+    public function isSetupComplete(IUser $user): bool {
118
+        return in_array($user->getUID(), $this->setupUsersComplete, true);
119
+    }
120
+
121
+    /**
122
+     * Checks if a path has been cached either directly or through a full setup
123
+     * of one of its parents.
124
+     */
125
+    private function isPathSetup(string $path): bool {
126
+        // if the exact path was already setup with or without children
127
+        if (array_key_exists($path, $this->setupMountProviderPaths)) {
128
+            return true;
129
+        }
130
+
131
+        // or if any of the ancestors was fully setup
132
+        while (($path = dirname($path)) !== '/') {
133
+            $setupPath = $this->setupMountProviderPaths[$path . '/'] ?? null;
134
+            if ($setupPath === self::SETUP_WITH_CHILDREN) {
135
+                return true;
136
+            }
137
+        }
138
+
139
+        return false;
140
+    }
141
+
142
+    private function setupBuiltinWrappers() {
143
+        if ($this->setupBuiltinWrappersDone) {
144
+            return;
145
+        }
146
+        $this->setupBuiltinWrappersDone = true;
147
+
148
+        // load all filesystem apps before, so no setup-hook gets lost
149
+        $this->appManager->loadApps(['filesystem']);
150
+        $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
151
+
152
+        Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
153
+            if ($storage->instanceOfStorage(Common::class)) {
154
+                $options = array_merge($mount->getOptions(), ['mount_point' => $mountPoint]);
155
+                $storage->setMountOptions($options);
156
+            }
157
+            return $storage;
158
+        });
159
+
160
+        $reSharingEnabled = Share::isResharingAllowed();
161
+        $user = $this->userSession->getUser();
162
+        $sharingEnabledForUser = $user ? !$this->shareDisableChecker->sharingDisabledForUser($user->getUID()) : true;
163
+        Filesystem::addStorageWrapper(
164
+            'sharing_mask',
165
+            function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
166
+                $sharingEnabledForMount = $mount->getOption('enable_sharing', true);
167
+                $isShared = $mount instanceof ISharedMountPoint;
168
+                if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
169
+                    return new PermissionsMask([
170
+                        'storage' => $storage,
171
+                        'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
172
+                    ]);
173
+                }
174
+                return $storage;
175
+            }
176
+        );
177
+
178
+        // install storage availability wrapper, before most other wrappers
179
+        Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
180
+            $externalMount = $mount instanceof ExternalMountPoint || $mount instanceof Mount;
181
+            if ($externalMount && !$storage->isLocal()) {
182
+                return new Availability(['storage' => $storage]);
183
+            }
184
+            return $storage;
185
+        });
186
+
187
+        Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
188
+            if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) {
189
+                return new Encoding(['storage' => $storage]);
190
+            }
191
+            return $storage;
192
+        });
193
+
194
+        $quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false);
195
+        Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
196
+            // set up quota for home storages, even for other users
197
+            // which can happen when using sharing
198
+            if ($mount instanceof HomeMountPoint) {
199
+                $user = $mount->getUser();
200
+                return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
201
+                    return $user->getQuotaBytes();
202
+                }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
203
+            }
204
+
205
+            return $storage;
206
+        });
207
+
208
+        Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
209
+            /*
210 210
 			 * Do not allow any operations that modify the storage
211 211
 			 */
212
-			if ($mount->getOption('readonly', false)) {
213
-				return new PermissionsMask([
214
-					'storage' => $storage,
215
-					'mask' => Constants::PERMISSION_ALL & ~(
216
-						Constants::PERMISSION_UPDATE
217
-						| Constants::PERMISSION_CREATE
218
-						| Constants::PERMISSION_DELETE
219
-					),
220
-				]);
221
-			}
222
-			return $storage;
223
-		});
224
-
225
-		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
226
-	}
227
-
228
-	/**
229
-	 * Setup the full filesystem for the specified user
230
-	 */
231
-	public function setupForUser(IUser $user): void {
232
-		if ($this->isSetupComplete($user)) {
233
-			return;
234
-		}
235
-		$this->setupUsersComplete[] = $user->getUID();
236
-
237
-		$this->eventLogger->start('fs:setup:user:full', 'Setup full filesystem for user');
238
-
239
-		$this->setupUserMountProviders[$user->getUID()] ??= [];
240
-		$previouslySetupProviders = $this->setupUserMountProviders[$user->getUID()];
241
-
242
-		$this->setupForUserWith($user, function () use ($user) {
243
-			$this->mountProviderCollection->addMountForUser($user, $this->mountManager, function (
244
-				string $providerClass,
245
-			) use ($user) {
246
-				return !in_array($providerClass, $this->setupUserMountProviders[$user->getUID()]);
247
-			});
248
-		});
249
-		$this->afterUserFullySetup($user, $previouslySetupProviders);
250
-		$this->eventLogger->end('fs:setup:user:full');
251
-	}
252
-
253
-	/**
254
-	 * Part of the user setup that is run only once per user.
255
-	 */
256
-	private function oneTimeUserSetup(IUser $user) {
257
-		if ($this->isSetupStarted($user)) {
258
-			return;
259
-		}
260
-		$this->setupUsers[] = $user->getUID();
261
-
262
-		$this->setupRoot();
263
-
264
-		$this->eventLogger->start('fs:setup:user:onetime', 'Onetime filesystem for user');
265
-
266
-		$this->setupBuiltinWrappers();
267
-
268
-		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
269
-
270
-		// TODO remove hook
271
-		OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]);
272
-
273
-		$event = new BeforeFileSystemSetupEvent($user);
274
-		$this->eventDispatcher->dispatchTyped($event);
275
-
276
-		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
277
-
278
-		$userDir = '/' . $user->getUID() . '/files';
279
-
280
-		Filesystem::initInternal($userDir);
281
-
282
-		if ($this->lockdownManager->canAccessFilesystem()) {
283
-			$this->eventLogger->start('fs:setup:user:home', 'Setup home filesystem for user');
284
-			// home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers
285
-			$homeMount = $this->mountProviderCollection->getHomeMountForUser($user);
286
-			$this->mountManager->addMount($homeMount);
287
-
288
-			if ($homeMount->getStorageRootId() === -1) {
289
-				$this->eventLogger->start('fs:setup:user:home:scan', 'Scan home filesystem for user');
290
-				$homeMount->getStorage()->mkdir('');
291
-				$homeMount->getStorage()->getScanner()->scan('');
292
-				$this->eventLogger->end('fs:setup:user:home:scan');
293
-			}
294
-			$this->eventLogger->end('fs:setup:user:home');
295
-		} else {
296
-			$this->mountManager->addMount(new MountPoint(
297
-				new NullStorage([]),
298
-				'/' . $user->getUID()
299
-			));
300
-			$this->mountManager->addMount(new MountPoint(
301
-				new NullStorage([]),
302
-				'/' . $user->getUID() . '/files'
303
-			));
304
-			$this->setupUsersComplete[] = $user->getUID();
305
-		}
306
-
307
-		$this->listenForNewMountProviders();
308
-
309
-		$this->eventLogger->end('fs:setup:user:onetime');
310
-	}
311
-
312
-	/**
313
-	 * Final housekeeping after a user has been fully setup
314
-	 */
315
-	private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void {
316
-		$this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup');
317
-		$userRoot = '/' . $user->getUID() . '/';
318
-		$mounts = $this->mountManager->getAll();
319
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
320
-			return str_starts_with($mount->getMountPoint(), $userRoot);
321
-		});
322
-		$allProviders = array_map(function (IMountProvider|IHomeMountProvider|IRootMountProvider $provider) {
323
-			return get_class($provider);
324
-		}, array_merge(
325
-			$this->mountProviderCollection->getProviders(),
326
-			$this->mountProviderCollection->getHomeProviders(),
327
-			$this->mountProviderCollection->getRootProviders(),
328
-		));
329
-		$newProviders = array_diff($allProviders, $previouslySetupProviders);
330
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
331
-			return !in_array($mount->getMountProvider(), $previouslySetupProviders);
332
-		});
333
-		$this->registerMounts($user, $mounts, $newProviders);
334
-
335
-		$cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60);
336
-		if ($cacheDuration > 0) {
337
-			$this->cache->set($user->getUID(), true, $cacheDuration);
338
-			$this->fullSetupRequired[$user->getUID()] = false;
339
-		}
340
-		$this->eventLogger->end('fs:setup:user:full:post');
341
-	}
342
-
343
-	/**
344
-	 * Executes the one-time user setup and, if the user can access the
345
-	 * filesystem, executes $mountCallback.
346
-	 *
347
-	 * @param IUser $user
348
-	 * @param IMountPoint $mounts
349
-	 * @return void
350
-	 * @throws \OCP\HintException
351
-	 * @throws \OC\ServerNotAvailableException
352
-	 * @see self::oneTimeUserSetup()
353
-	 *
354
-	 */
355
-	private function setupForUserWith(IUser $user, callable $mountCallback): void {
356
-		$this->oneTimeUserSetup($user);
357
-
358
-		if ($this->lockdownManager->canAccessFilesystem()) {
359
-			$mountCallback();
360
-		}
361
-		$this->eventLogger->start('fs:setup:user:post-init-mountpoint', 'post_initMountPoints legacy hook');
362
-		\OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]);
363
-		$this->eventLogger->end('fs:setup:user:post-init-mountpoint');
364
-
365
-		$userDir = '/' . $user->getUID() . '/files';
366
-		$this->eventLogger->start('fs:setup:user:setup-hook', 'setup legacy hook');
367
-		OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]);
368
-		$this->eventLogger->end('fs:setup:user:setup-hook');
369
-	}
370
-
371
-	/**
372
-	 * Set up the root filesystem
373
-	 */
374
-	public function setupRoot(): void {
375
-		//setting up the filesystem twice can only lead to trouble
376
-		if ($this->rootSetup) {
377
-			return;
378
-		}
379
-
380
-		$this->setupBuiltinWrappers();
381
-
382
-		$this->rootSetup = true;
383
-
384
-		$this->eventLogger->start('fs:setup:root', 'Setup root filesystem');
385
-
386
-		$rootMounts = $this->mountProviderCollection->getRootMounts();
387
-		foreach ($rootMounts as $rootMountProvider) {
388
-			$this->mountManager->addMount($rootMountProvider);
389
-		}
390
-
391
-		$this->eventLogger->end('fs:setup:root');
392
-	}
393
-
394
-	/**
395
-	 * Get the user to setup for a path or `null` if the root needs to be setup
396
-	 *
397
-	 * @param string $path
398
-	 * @return IUser|null
399
-	 */
400
-	private function getUserForPath(string $path) {
401
-		if (str_starts_with($path, '/__groupfolders')) {
402
-			return null;
403
-		} elseif (substr_count($path, '/') < 2) {
404
-			if ($user = $this->userSession->getUser()) {
405
-				return $user;
406
-			} else {
407
-				return null;
408
-			}
409
-		} elseif (str_starts_with($path, '/appdata_' . \OC_Util::getInstanceId()) || str_starts_with($path, '/files_external/')) {
410
-			return null;
411
-		} else {
412
-			[, $userId] = explode('/', $path);
413
-		}
414
-
415
-		return $this->userManager->get($userId);
416
-	}
417
-
418
-	/**
419
-	 * Set up the filesystem for the specified path, optionally including all
420
-	 * children mounts.
421
-	 */
422
-	public function setupForPath(string $path, bool $includeChildren = false): void {
423
-		$user = $this->getUserForPath($path);
424
-		if (!$user) {
425
-			$this->setupRoot();
426
-			return;
427
-		}
428
-
429
-		if ($this->isSetupComplete($user)) {
430
-			return;
431
-		}
432
-
433
-		if ($this->fullSetupRequired($user)) {
434
-			$this->setupForUser($user);
435
-			return;
436
-		}
437
-
438
-		// for the user's home folder, and includes children we need everything always
439
-		if (rtrim($path) === '/' . $user->getUID() . '/files' && $includeChildren) {
440
-			$this->setupForUser($user);
441
-			return;
442
-		}
443
-
444
-		if (!isset($this->setupUserMountProviders[$user->getUID()])) {
445
-			$this->setupUserMountProviders[$user->getUID()] = [];
446
-		}
447
-		$setupProviders = &$this->setupUserMountProviders[$user->getUID()];
448
-		$currentProviders = [];
449
-
450
-		try {
451
-			$cachedMount = $this->userMountCache->getMountForPath($user, $path);
452
-		} catch (NotFoundException $e) {
453
-			$this->setupForUser($user);
454
-			return;
455
-		}
456
-
457
-		$this->oneTimeUserSetup($user);
458
-
459
-		$this->eventLogger->start('fs:setup:user:path', "Setup $path filesystem for user");
460
-		$this->eventLogger->start('fs:setup:user:path:find', "Find mountpoint for $path");
461
-
462
-		$fullProviderMounts = [];
463
-		$authoritativeMounts = [];
464
-
465
-		$mountProvider = $cachedMount->getMountProvider();
466
-		$mountPoint = $cachedMount->getMountPoint();
467
-		$isMountProviderSetup = in_array($mountProvider, $setupProviders);
468
-		$isPathSetupAsAuthoritative
469
-			= $this->isPathSetup($mountPoint);
470
-		if (!$isMountProviderSetup && !$isPathSetupAsAuthoritative) {
471
-			if ($mountProvider === '') {
472
-				$this->logger->debug('mount at ' . $cachedMount->getMountPoint() . ' has no provider set, performing full setup');
473
-				$this->eventLogger->end('fs:setup:user:path:find');
474
-				$this->setupForUser($user);
475
-				$this->eventLogger->end('fs:setup:user:path');
476
-				return;
477
-			}
478
-
479
-			if (is_a($mountProvider, IPartialMountProvider::class, true)) {
480
-				$rootId = $cachedMount->getRootId();
481
-				$rootMetadata = $this->fileAccess->getByFileId($rootId);
482
-				$providerArgs = new IMountProviderArgs($cachedMount, $rootMetadata);
483
-				// mark the path as cached (without children for now...)
484
-				$this->setupMountProviderPaths[$mountPoint] = self::SETUP_WITHOUT_CHILDREN;
485
-				$authoritativeMounts[] = array_values(
486
-					$this->mountProviderCollection->getUserMountsFromProviderByPath(
487
-						$mountProvider,
488
-						$path,
489
-						[$providerArgs]
490
-					)
491
-				);
492
-			} else {
493
-				$currentProviders[] = $mountProvider;
494
-				$setupProviders[] = $mountProvider;
495
-				$fullProviderMounts[]
496
-					= $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$mountProvider]);
497
-			}
498
-		}
499
-
500
-		if ($includeChildren) {
501
-			$subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
502
-			$this->eventLogger->end('fs:setup:user:path:find');
503
-
504
-			$needsFullSetup
505
-				= array_any(
506
-					$subCachedMounts,
507
-					fn (ICachedMountInfo $info) => $info->getMountProvider() === ''
508
-				);
509
-
510
-			if ($needsFullSetup) {
511
-				$this->logger->debug('mount has no provider set, performing full setup');
512
-				$this->setupForUser($user);
513
-				$this->eventLogger->end('fs:setup:user:path');
514
-				return;
515
-			}
516
-
517
-			/** @var array<class-string<IMountProvider>, ICachedMountInfo[]> $authoritativeCachedMounts */
518
-			$authoritativeCachedMounts = [];
519
-			foreach ($subCachedMounts as $cachedMount) {
520
-				/** @var class-string<IMountProvider> $mountProvider */
521
-				$mountProvider = $cachedMount->getMountProvider();
522
-
523
-				// skip setup for already set up providers
524
-				if (in_array($mountProvider, $setupProviders)) {
525
-					continue;
526
-				}
527
-
528
-				if (is_a($mountProvider, IPartialMountProvider::class, true)) {
529
-					// skip setup if path was set up as authoritative before
530
-					if ($this->isPathSetup($cachedMount->getMountPoint())) {
531
-						continue;
532
-					}
533
-					// collect cached mount points for authoritative providers
534
-					$authoritativeCachedMounts[$mountProvider] ??= [];
535
-					$authoritativeCachedMounts[$mountProvider][] = $cachedMount;
536
-					continue;
537
-				}
538
-
539
-				$currentProviders[] = $mountProvider;
540
-				$setupProviders[] = $mountProvider;
541
-				$fullProviderMounts[]
542
-					= $this->mountProviderCollection->getUserMountsForProviderClasses(
543
-						$user,
544
-						[$mountProvider]
545
-					);
546
-			}
547
-
548
-			if (!empty($authoritativeCachedMounts)) {
549
-				$rootIds = array_map(
550
-					fn (ICachedMountInfo $mount) => $mount->getRootId(),
551
-					array_merge(...array_values($authoritativeCachedMounts)),
552
-				);
553
-
554
-				$rootsMetadata = [];
555
-				foreach (array_chunk($rootIds, 1000) as $chunk) {
556
-					foreach ($this->fileAccess->getByFileIds($chunk) as $id => $fileMetadata) {
557
-						$rootsMetadata[$id] = $fileMetadata;
558
-					}
559
-				}
560
-				$this->setupMountProviderPaths[$mountPoint] = self::SETUP_WITH_CHILDREN;
561
-				foreach ($authoritativeCachedMounts as $providerClass => $cachedMounts) {
562
-					$providerArgs = array_filter(array_map(
563
-						static function (ICachedMountInfo $info) use ($rootsMetadata) {
564
-							$rootMetadata = $rootsMetadata[$info->getRootId()] ?? null;
565
-
566
-							return $rootMetadata
567
-								? new IMountProviderArgs($info, $rootMetadata)
568
-								: null;
569
-						},
570
-						$cachedMounts
571
-					));
572
-					$authoritativeMounts[]
573
-						= $this->mountProviderCollection->getUserMountsFromProviderByPath(
574
-							$providerClass,
575
-							$path,
576
-							$providerArgs,
577
-						);
578
-				}
579
-			}
580
-		} else {
581
-			$this->eventLogger->end('fs:setup:user:path:find');
582
-		}
583
-
584
-		$fullProviderMounts = array_merge(...$fullProviderMounts);
585
-		$authoritativeMounts = array_merge(...$authoritativeMounts);
586
-
587
-		if (count($fullProviderMounts) || count($authoritativeMounts)) {
588
-			if (count($fullProviderMounts)) {
589
-				$this->registerMounts($user, $fullProviderMounts, $currentProviders);
590
-			}
591
-
592
-			$this->setupForUserWith($user, function () use ($fullProviderMounts, $authoritativeMounts) {
593
-				$allMounts = [...$fullProviderMounts, ...$authoritativeMounts];
594
-				array_walk($allMounts, $this->mountManager->addMount(...));
595
-			});
596
-		} elseif (!$this->isSetupStarted($user)) {
597
-			$this->oneTimeUserSetup($user);
598
-		}
599
-		$this->eventLogger->end('fs:setup:user:path');
600
-	}
601
-
602
-	private function fullSetupRequired(IUser $user): bool {
603
-		if ($this->forceFullSetup) {
604
-			return true;
605
-		}
606
-
607
-		// we perform a "cached" setup only after having done the full setup recently
608
-		// this is also used to trigger a full setup after handling events that are likely
609
-		// to change the available mounts
610
-		if (!isset($this->fullSetupRequired[$user->getUID()])) {
611
-			$this->fullSetupRequired[$user->getUID()] = !$this->cache->get($user->getUID());
612
-		}
613
-		return $this->fullSetupRequired[$user->getUID()];
614
-	}
615
-
616
-	/**
617
-	 * @param string $path
618
-	 * @param string[] $providers
619
-	 */
620
-	public function setupForProvider(string $path, array $providers): void {
621
-		$user = $this->getUserForPath($path);
622
-		if (!$user) {
623
-			$this->setupRoot();
624
-			return;
625
-		}
626
-
627
-		if ($this->isSetupComplete($user)) {
628
-			return;
629
-		}
630
-
631
-		if ($this->fullSetupRequired($user)) {
632
-			$this->setupForUser($user);
633
-			return;
634
-		}
635
-
636
-		$this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
637
-
638
-		$this->oneTimeUserSetup($user);
639
-
640
-		// home providers are always used
641
-		$providers = array_filter($providers, function (string $provider) {
642
-			return !is_subclass_of($provider, IHomeMountProvider::class);
643
-		});
644
-
645
-		if (in_array('', $providers)) {
646
-			$this->setupForUser($user);
647
-			return;
648
-		}
649
-		$setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
650
-
651
-		$providers = array_diff($providers, $setupProviders);
652
-		if (count($providers) === 0) {
653
-			if (!$this->isSetupStarted($user)) {
654
-				$this->oneTimeUserSetup($user);
655
-			}
656
-			$this->eventLogger->end('fs:setup:user:providers');
657
-			return;
658
-		} else {
659
-			$this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
660
-			$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
661
-		}
662
-
663
-		$this->registerMounts($user, $mounts, $providers);
664
-		$this->setupForUserWith($user, function () use ($mounts) {
665
-			array_walk($mounts, [$this->mountManager, 'addMount']);
666
-		});
667
-		$this->eventLogger->end('fs:setup:user:providers');
668
-	}
669
-
670
-	public function tearDown() {
671
-		$this->setupUsers = [];
672
-		$this->setupUsersComplete = [];
673
-		$this->setupUserMountProviders = [];
674
-		$this->setupMountProviderPaths = [];
675
-		$this->fullSetupRequired = [];
676
-		$this->rootSetup = false;
677
-		$this->mountManager->clear();
678
-		$this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
679
-	}
680
-
681
-	/**
682
-	 * Get mounts from mount providers that are registered after setup
683
-	 */
684
-	private function listenForNewMountProviders() {
685
-		if (!$this->listeningForProviders) {
686
-			$this->listeningForProviders = true;
687
-			$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
688
-				IMountProvider $provider,
689
-			) {
690
-				foreach ($this->setupUsers as $userId) {
691
-					$user = $this->userManager->get($userId);
692
-					if ($user) {
693
-						$mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
694
-						array_walk($mounts, [$this->mountManager, 'addMount']);
695
-					}
696
-				}
697
-			});
698
-		}
699
-	}
700
-
701
-	private function setupListeners() {
702
-		// note that this event handling is intentionally pessimistic
703
-		// clearing the cache to often is better than not enough
704
-
705
-		$this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
706
-			$this->cache->remove($event->getUser()->getUID());
707
-		});
708
-		$this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
709
-			$this->cache->remove($event->getUser()->getUID());
710
-		});
711
-		$this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
712
-			$this->cache->remove($event->getShare()->getSharedWith());
713
-		});
714
-		$this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
715
-		) {
716
-			if ($user = $event->getUser()) {
717
-				$this->cache->remove($user->getUID());
718
-			} else {
719
-				$this->cache->clear();
720
-			}
721
-		});
722
-
723
-		$genericEvents = [
724
-			'OCA\Circles\Events\CreatingCircleEvent',
725
-			'OCA\Circles\Events\DestroyingCircleEvent',
726
-			'OCA\Circles\Events\AddingCircleMemberEvent',
727
-			'OCA\Circles\Events\RemovingCircleMemberEvent',
728
-		];
729
-
730
-		foreach ($genericEvents as $genericEvent) {
731
-			$this->eventDispatcher->addListener($genericEvent, function ($event) {
732
-				$this->cache->clear();
733
-			});
734
-		}
735
-	}
736
-
737
-	private function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null): void {
738
-		if ($this->lockdownManager->canAccessFilesystem()) {
739
-			$this->userMountCache->registerMounts($user, $mounts, $mountProviderClasses);
740
-		}
741
-	}
212
+            if ($mount->getOption('readonly', false)) {
213
+                return new PermissionsMask([
214
+                    'storage' => $storage,
215
+                    'mask' => Constants::PERMISSION_ALL & ~(
216
+                        Constants::PERMISSION_UPDATE
217
+                        | Constants::PERMISSION_CREATE
218
+                        | Constants::PERMISSION_DELETE
219
+                    ),
220
+                ]);
221
+            }
222
+            return $storage;
223
+        });
224
+
225
+        Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
226
+    }
227
+
228
+    /**
229
+     * Setup the full filesystem for the specified user
230
+     */
231
+    public function setupForUser(IUser $user): void {
232
+        if ($this->isSetupComplete($user)) {
233
+            return;
234
+        }
235
+        $this->setupUsersComplete[] = $user->getUID();
236
+
237
+        $this->eventLogger->start('fs:setup:user:full', 'Setup full filesystem for user');
238
+
239
+        $this->setupUserMountProviders[$user->getUID()] ??= [];
240
+        $previouslySetupProviders = $this->setupUserMountProviders[$user->getUID()];
241
+
242
+        $this->setupForUserWith($user, function () use ($user) {
243
+            $this->mountProviderCollection->addMountForUser($user, $this->mountManager, function (
244
+                string $providerClass,
245
+            ) use ($user) {
246
+                return !in_array($providerClass, $this->setupUserMountProviders[$user->getUID()]);
247
+            });
248
+        });
249
+        $this->afterUserFullySetup($user, $previouslySetupProviders);
250
+        $this->eventLogger->end('fs:setup:user:full');
251
+    }
252
+
253
+    /**
254
+     * Part of the user setup that is run only once per user.
255
+     */
256
+    private function oneTimeUserSetup(IUser $user) {
257
+        if ($this->isSetupStarted($user)) {
258
+            return;
259
+        }
260
+        $this->setupUsers[] = $user->getUID();
261
+
262
+        $this->setupRoot();
263
+
264
+        $this->eventLogger->start('fs:setup:user:onetime', 'Onetime filesystem for user');
265
+
266
+        $this->setupBuiltinWrappers();
267
+
268
+        $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
269
+
270
+        // TODO remove hook
271
+        OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]);
272
+
273
+        $event = new BeforeFileSystemSetupEvent($user);
274
+        $this->eventDispatcher->dispatchTyped($event);
275
+
276
+        Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
277
+
278
+        $userDir = '/' . $user->getUID() . '/files';
279
+
280
+        Filesystem::initInternal($userDir);
281
+
282
+        if ($this->lockdownManager->canAccessFilesystem()) {
283
+            $this->eventLogger->start('fs:setup:user:home', 'Setup home filesystem for user');
284
+            // home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers
285
+            $homeMount = $this->mountProviderCollection->getHomeMountForUser($user);
286
+            $this->mountManager->addMount($homeMount);
287
+
288
+            if ($homeMount->getStorageRootId() === -1) {
289
+                $this->eventLogger->start('fs:setup:user:home:scan', 'Scan home filesystem for user');
290
+                $homeMount->getStorage()->mkdir('');
291
+                $homeMount->getStorage()->getScanner()->scan('');
292
+                $this->eventLogger->end('fs:setup:user:home:scan');
293
+            }
294
+            $this->eventLogger->end('fs:setup:user:home');
295
+        } else {
296
+            $this->mountManager->addMount(new MountPoint(
297
+                new NullStorage([]),
298
+                '/' . $user->getUID()
299
+            ));
300
+            $this->mountManager->addMount(new MountPoint(
301
+                new NullStorage([]),
302
+                '/' . $user->getUID() . '/files'
303
+            ));
304
+            $this->setupUsersComplete[] = $user->getUID();
305
+        }
306
+
307
+        $this->listenForNewMountProviders();
308
+
309
+        $this->eventLogger->end('fs:setup:user:onetime');
310
+    }
311
+
312
+    /**
313
+     * Final housekeeping after a user has been fully setup
314
+     */
315
+    private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void {
316
+        $this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup');
317
+        $userRoot = '/' . $user->getUID() . '/';
318
+        $mounts = $this->mountManager->getAll();
319
+        $mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
320
+            return str_starts_with($mount->getMountPoint(), $userRoot);
321
+        });
322
+        $allProviders = array_map(function (IMountProvider|IHomeMountProvider|IRootMountProvider $provider) {
323
+            return get_class($provider);
324
+        }, array_merge(
325
+            $this->mountProviderCollection->getProviders(),
326
+            $this->mountProviderCollection->getHomeProviders(),
327
+            $this->mountProviderCollection->getRootProviders(),
328
+        ));
329
+        $newProviders = array_diff($allProviders, $previouslySetupProviders);
330
+        $mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
331
+            return !in_array($mount->getMountProvider(), $previouslySetupProviders);
332
+        });
333
+        $this->registerMounts($user, $mounts, $newProviders);
334
+
335
+        $cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60);
336
+        if ($cacheDuration > 0) {
337
+            $this->cache->set($user->getUID(), true, $cacheDuration);
338
+            $this->fullSetupRequired[$user->getUID()] = false;
339
+        }
340
+        $this->eventLogger->end('fs:setup:user:full:post');
341
+    }
342
+
343
+    /**
344
+     * Executes the one-time user setup and, if the user can access the
345
+     * filesystem, executes $mountCallback.
346
+     *
347
+     * @param IUser $user
348
+     * @param IMountPoint $mounts
349
+     * @return void
350
+     * @throws \OCP\HintException
351
+     * @throws \OC\ServerNotAvailableException
352
+     * @see self::oneTimeUserSetup()
353
+     *
354
+     */
355
+    private function setupForUserWith(IUser $user, callable $mountCallback): void {
356
+        $this->oneTimeUserSetup($user);
357
+
358
+        if ($this->lockdownManager->canAccessFilesystem()) {
359
+            $mountCallback();
360
+        }
361
+        $this->eventLogger->start('fs:setup:user:post-init-mountpoint', 'post_initMountPoints legacy hook');
362
+        \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]);
363
+        $this->eventLogger->end('fs:setup:user:post-init-mountpoint');
364
+
365
+        $userDir = '/' . $user->getUID() . '/files';
366
+        $this->eventLogger->start('fs:setup:user:setup-hook', 'setup legacy hook');
367
+        OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]);
368
+        $this->eventLogger->end('fs:setup:user:setup-hook');
369
+    }
370
+
371
+    /**
372
+     * Set up the root filesystem
373
+     */
374
+    public function setupRoot(): void {
375
+        //setting up the filesystem twice can only lead to trouble
376
+        if ($this->rootSetup) {
377
+            return;
378
+        }
379
+
380
+        $this->setupBuiltinWrappers();
381
+
382
+        $this->rootSetup = true;
383
+
384
+        $this->eventLogger->start('fs:setup:root', 'Setup root filesystem');
385
+
386
+        $rootMounts = $this->mountProviderCollection->getRootMounts();
387
+        foreach ($rootMounts as $rootMountProvider) {
388
+            $this->mountManager->addMount($rootMountProvider);
389
+        }
390
+
391
+        $this->eventLogger->end('fs:setup:root');
392
+    }
393
+
394
+    /**
395
+     * Get the user to setup for a path or `null` if the root needs to be setup
396
+     *
397
+     * @param string $path
398
+     * @return IUser|null
399
+     */
400
+    private function getUserForPath(string $path) {
401
+        if (str_starts_with($path, '/__groupfolders')) {
402
+            return null;
403
+        } elseif (substr_count($path, '/') < 2) {
404
+            if ($user = $this->userSession->getUser()) {
405
+                return $user;
406
+            } else {
407
+                return null;
408
+            }
409
+        } elseif (str_starts_with($path, '/appdata_' . \OC_Util::getInstanceId()) || str_starts_with($path, '/files_external/')) {
410
+            return null;
411
+        } else {
412
+            [, $userId] = explode('/', $path);
413
+        }
414
+
415
+        return $this->userManager->get($userId);
416
+    }
417
+
418
+    /**
419
+     * Set up the filesystem for the specified path, optionally including all
420
+     * children mounts.
421
+     */
422
+    public function setupForPath(string $path, bool $includeChildren = false): void {
423
+        $user = $this->getUserForPath($path);
424
+        if (!$user) {
425
+            $this->setupRoot();
426
+            return;
427
+        }
428
+
429
+        if ($this->isSetupComplete($user)) {
430
+            return;
431
+        }
432
+
433
+        if ($this->fullSetupRequired($user)) {
434
+            $this->setupForUser($user);
435
+            return;
436
+        }
437
+
438
+        // for the user's home folder, and includes children we need everything always
439
+        if (rtrim($path) === '/' . $user->getUID() . '/files' && $includeChildren) {
440
+            $this->setupForUser($user);
441
+            return;
442
+        }
443
+
444
+        if (!isset($this->setupUserMountProviders[$user->getUID()])) {
445
+            $this->setupUserMountProviders[$user->getUID()] = [];
446
+        }
447
+        $setupProviders = &$this->setupUserMountProviders[$user->getUID()];
448
+        $currentProviders = [];
449
+
450
+        try {
451
+            $cachedMount = $this->userMountCache->getMountForPath($user, $path);
452
+        } catch (NotFoundException $e) {
453
+            $this->setupForUser($user);
454
+            return;
455
+        }
456
+
457
+        $this->oneTimeUserSetup($user);
458
+
459
+        $this->eventLogger->start('fs:setup:user:path', "Setup $path filesystem for user");
460
+        $this->eventLogger->start('fs:setup:user:path:find', "Find mountpoint for $path");
461
+
462
+        $fullProviderMounts = [];
463
+        $authoritativeMounts = [];
464
+
465
+        $mountProvider = $cachedMount->getMountProvider();
466
+        $mountPoint = $cachedMount->getMountPoint();
467
+        $isMountProviderSetup = in_array($mountProvider, $setupProviders);
468
+        $isPathSetupAsAuthoritative
469
+            = $this->isPathSetup($mountPoint);
470
+        if (!$isMountProviderSetup && !$isPathSetupAsAuthoritative) {
471
+            if ($mountProvider === '') {
472
+                $this->logger->debug('mount at ' . $cachedMount->getMountPoint() . ' has no provider set, performing full setup');
473
+                $this->eventLogger->end('fs:setup:user:path:find');
474
+                $this->setupForUser($user);
475
+                $this->eventLogger->end('fs:setup:user:path');
476
+                return;
477
+            }
478
+
479
+            if (is_a($mountProvider, IPartialMountProvider::class, true)) {
480
+                $rootId = $cachedMount->getRootId();
481
+                $rootMetadata = $this->fileAccess->getByFileId($rootId);
482
+                $providerArgs = new IMountProviderArgs($cachedMount, $rootMetadata);
483
+                // mark the path as cached (without children for now...)
484
+                $this->setupMountProviderPaths[$mountPoint] = self::SETUP_WITHOUT_CHILDREN;
485
+                $authoritativeMounts[] = array_values(
486
+                    $this->mountProviderCollection->getUserMountsFromProviderByPath(
487
+                        $mountProvider,
488
+                        $path,
489
+                        [$providerArgs]
490
+                    )
491
+                );
492
+            } else {
493
+                $currentProviders[] = $mountProvider;
494
+                $setupProviders[] = $mountProvider;
495
+                $fullProviderMounts[]
496
+                    = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$mountProvider]);
497
+            }
498
+        }
499
+
500
+        if ($includeChildren) {
501
+            $subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
502
+            $this->eventLogger->end('fs:setup:user:path:find');
503
+
504
+            $needsFullSetup
505
+                = array_any(
506
+                    $subCachedMounts,
507
+                    fn (ICachedMountInfo $info) => $info->getMountProvider() === ''
508
+                );
509
+
510
+            if ($needsFullSetup) {
511
+                $this->logger->debug('mount has no provider set, performing full setup');
512
+                $this->setupForUser($user);
513
+                $this->eventLogger->end('fs:setup:user:path');
514
+                return;
515
+            }
516
+
517
+            /** @var array<class-string<IMountProvider>, ICachedMountInfo[]> $authoritativeCachedMounts */
518
+            $authoritativeCachedMounts = [];
519
+            foreach ($subCachedMounts as $cachedMount) {
520
+                /** @var class-string<IMountProvider> $mountProvider */
521
+                $mountProvider = $cachedMount->getMountProvider();
522
+
523
+                // skip setup for already set up providers
524
+                if (in_array($mountProvider, $setupProviders)) {
525
+                    continue;
526
+                }
527
+
528
+                if (is_a($mountProvider, IPartialMountProvider::class, true)) {
529
+                    // skip setup if path was set up as authoritative before
530
+                    if ($this->isPathSetup($cachedMount->getMountPoint())) {
531
+                        continue;
532
+                    }
533
+                    // collect cached mount points for authoritative providers
534
+                    $authoritativeCachedMounts[$mountProvider] ??= [];
535
+                    $authoritativeCachedMounts[$mountProvider][] = $cachedMount;
536
+                    continue;
537
+                }
538
+
539
+                $currentProviders[] = $mountProvider;
540
+                $setupProviders[] = $mountProvider;
541
+                $fullProviderMounts[]
542
+                    = $this->mountProviderCollection->getUserMountsForProviderClasses(
543
+                        $user,
544
+                        [$mountProvider]
545
+                    );
546
+            }
547
+
548
+            if (!empty($authoritativeCachedMounts)) {
549
+                $rootIds = array_map(
550
+                    fn (ICachedMountInfo $mount) => $mount->getRootId(),
551
+                    array_merge(...array_values($authoritativeCachedMounts)),
552
+                );
553
+
554
+                $rootsMetadata = [];
555
+                foreach (array_chunk($rootIds, 1000) as $chunk) {
556
+                    foreach ($this->fileAccess->getByFileIds($chunk) as $id => $fileMetadata) {
557
+                        $rootsMetadata[$id] = $fileMetadata;
558
+                    }
559
+                }
560
+                $this->setupMountProviderPaths[$mountPoint] = self::SETUP_WITH_CHILDREN;
561
+                foreach ($authoritativeCachedMounts as $providerClass => $cachedMounts) {
562
+                    $providerArgs = array_filter(array_map(
563
+                        static function (ICachedMountInfo $info) use ($rootsMetadata) {
564
+                            $rootMetadata = $rootsMetadata[$info->getRootId()] ?? null;
565
+
566
+                            return $rootMetadata
567
+                                ? new IMountProviderArgs($info, $rootMetadata)
568
+                                : null;
569
+                        },
570
+                        $cachedMounts
571
+                    ));
572
+                    $authoritativeMounts[]
573
+                        = $this->mountProviderCollection->getUserMountsFromProviderByPath(
574
+                            $providerClass,
575
+                            $path,
576
+                            $providerArgs,
577
+                        );
578
+                }
579
+            }
580
+        } else {
581
+            $this->eventLogger->end('fs:setup:user:path:find');
582
+        }
583
+
584
+        $fullProviderMounts = array_merge(...$fullProviderMounts);
585
+        $authoritativeMounts = array_merge(...$authoritativeMounts);
586
+
587
+        if (count($fullProviderMounts) || count($authoritativeMounts)) {
588
+            if (count($fullProviderMounts)) {
589
+                $this->registerMounts($user, $fullProviderMounts, $currentProviders);
590
+            }
591
+
592
+            $this->setupForUserWith($user, function () use ($fullProviderMounts, $authoritativeMounts) {
593
+                $allMounts = [...$fullProviderMounts, ...$authoritativeMounts];
594
+                array_walk($allMounts, $this->mountManager->addMount(...));
595
+            });
596
+        } elseif (!$this->isSetupStarted($user)) {
597
+            $this->oneTimeUserSetup($user);
598
+        }
599
+        $this->eventLogger->end('fs:setup:user:path');
600
+    }
601
+
602
+    private function fullSetupRequired(IUser $user): bool {
603
+        if ($this->forceFullSetup) {
604
+            return true;
605
+        }
606
+
607
+        // we perform a "cached" setup only after having done the full setup recently
608
+        // this is also used to trigger a full setup after handling events that are likely
609
+        // to change the available mounts
610
+        if (!isset($this->fullSetupRequired[$user->getUID()])) {
611
+            $this->fullSetupRequired[$user->getUID()] = !$this->cache->get($user->getUID());
612
+        }
613
+        return $this->fullSetupRequired[$user->getUID()];
614
+    }
615
+
616
+    /**
617
+     * @param string $path
618
+     * @param string[] $providers
619
+     */
620
+    public function setupForProvider(string $path, array $providers): void {
621
+        $user = $this->getUserForPath($path);
622
+        if (!$user) {
623
+            $this->setupRoot();
624
+            return;
625
+        }
626
+
627
+        if ($this->isSetupComplete($user)) {
628
+            return;
629
+        }
630
+
631
+        if ($this->fullSetupRequired($user)) {
632
+            $this->setupForUser($user);
633
+            return;
634
+        }
635
+
636
+        $this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
637
+
638
+        $this->oneTimeUserSetup($user);
639
+
640
+        // home providers are always used
641
+        $providers = array_filter($providers, function (string $provider) {
642
+            return !is_subclass_of($provider, IHomeMountProvider::class);
643
+        });
644
+
645
+        if (in_array('', $providers)) {
646
+            $this->setupForUser($user);
647
+            return;
648
+        }
649
+        $setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
650
+
651
+        $providers = array_diff($providers, $setupProviders);
652
+        if (count($providers) === 0) {
653
+            if (!$this->isSetupStarted($user)) {
654
+                $this->oneTimeUserSetup($user);
655
+            }
656
+            $this->eventLogger->end('fs:setup:user:providers');
657
+            return;
658
+        } else {
659
+            $this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
660
+            $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
661
+        }
662
+
663
+        $this->registerMounts($user, $mounts, $providers);
664
+        $this->setupForUserWith($user, function () use ($mounts) {
665
+            array_walk($mounts, [$this->mountManager, 'addMount']);
666
+        });
667
+        $this->eventLogger->end('fs:setup:user:providers');
668
+    }
669
+
670
+    public function tearDown() {
671
+        $this->setupUsers = [];
672
+        $this->setupUsersComplete = [];
673
+        $this->setupUserMountProviders = [];
674
+        $this->setupMountProviderPaths = [];
675
+        $this->fullSetupRequired = [];
676
+        $this->rootSetup = false;
677
+        $this->mountManager->clear();
678
+        $this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
679
+    }
680
+
681
+    /**
682
+     * Get mounts from mount providers that are registered after setup
683
+     */
684
+    private function listenForNewMountProviders() {
685
+        if (!$this->listeningForProviders) {
686
+            $this->listeningForProviders = true;
687
+            $this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
688
+                IMountProvider $provider,
689
+            ) {
690
+                foreach ($this->setupUsers as $userId) {
691
+                    $user = $this->userManager->get($userId);
692
+                    if ($user) {
693
+                        $mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
694
+                        array_walk($mounts, [$this->mountManager, 'addMount']);
695
+                    }
696
+                }
697
+            });
698
+        }
699
+    }
700
+
701
+    private function setupListeners() {
702
+        // note that this event handling is intentionally pessimistic
703
+        // clearing the cache to often is better than not enough
704
+
705
+        $this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
706
+            $this->cache->remove($event->getUser()->getUID());
707
+        });
708
+        $this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
709
+            $this->cache->remove($event->getUser()->getUID());
710
+        });
711
+        $this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
712
+            $this->cache->remove($event->getShare()->getSharedWith());
713
+        });
714
+        $this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
715
+        ) {
716
+            if ($user = $event->getUser()) {
717
+                $this->cache->remove($user->getUID());
718
+            } else {
719
+                $this->cache->clear();
720
+            }
721
+        });
722
+
723
+        $genericEvents = [
724
+            'OCA\Circles\Events\CreatingCircleEvent',
725
+            'OCA\Circles\Events\DestroyingCircleEvent',
726
+            'OCA\Circles\Events\AddingCircleMemberEvent',
727
+            'OCA\Circles\Events\RemovingCircleMemberEvent',
728
+        ];
729
+
730
+        foreach ($genericEvents as $genericEvent) {
731
+            $this->eventDispatcher->addListener($genericEvent, function ($event) {
732
+                $this->cache->clear();
733
+            });
734
+        }
735
+    }
736
+
737
+    private function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null): void {
738
+        if ($this->lockdownManager->canAccessFilesystem()) {
739
+            $this->userMountCache->registerMounts($user, $mounts, $mountProviderClasses);
740
+        }
741
+    }
742 742
 }
Please login to merge, or discard this patch.
Spacing   +32 added lines, -32 removed lines patch added patch discarded remove patch
@@ -130,7 +130,7 @@  discard block
 block discarded – undo
130 130
 
131 131
 		// or if any of the ancestors was fully setup
132 132
 		while (($path = dirname($path)) !== '/') {
133
-			$setupPath = $this->setupMountProviderPaths[$path . '/'] ?? null;
133
+			$setupPath = $this->setupMountProviderPaths[$path.'/'] ?? null;
134 134
 			if ($setupPath === self::SETUP_WITH_CHILDREN) {
135 135
 				return true;
136 136
 			}
@@ -149,7 +149,7 @@  discard block
 block discarded – undo
149 149
 		$this->appManager->loadApps(['filesystem']);
150 150
 		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
151 151
 
152
-		Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
152
+		Filesystem::addStorageWrapper('mount_options', function($mountPoint, IStorage $storage, IMountPoint $mount) {
153 153
 			if ($storage->instanceOfStorage(Common::class)) {
154 154
 				$options = array_merge($mount->getOptions(), ['mount_point' => $mountPoint]);
155 155
 				$storage->setMountOptions($options);
@@ -162,7 +162,7 @@  discard block
 block discarded – undo
162 162
 		$sharingEnabledForUser = $user ? !$this->shareDisableChecker->sharingDisabledForUser($user->getUID()) : true;
163 163
 		Filesystem::addStorageWrapper(
164 164
 			'sharing_mask',
165
-			function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
165
+			function($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
166 166
 				$sharingEnabledForMount = $mount->getOption('enable_sharing', true);
167 167
 				$isShared = $mount instanceof ISharedMountPoint;
168 168
 				if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
@@ -176,7 +176,7 @@  discard block
 block discarded – undo
176 176
 		);
177 177
 
178 178
 		// install storage availability wrapper, before most other wrappers
179
-		Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
179
+		Filesystem::addStorageWrapper('oc_availability', function($mountPoint, IStorage $storage, IMountPoint $mount) {
180 180
 			$externalMount = $mount instanceof ExternalMountPoint || $mount instanceof Mount;
181 181
 			if ($externalMount && !$storage->isLocal()) {
182 182
 				return new Availability(['storage' => $storage]);
@@ -184,7 +184,7 @@  discard block
 block discarded – undo
184 184
 			return $storage;
185 185
 		});
186 186
 
187
-		Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
187
+		Filesystem::addStorageWrapper('oc_encoding', function($mountPoint, IStorage $storage, IMountPoint $mount) {
188 188
 			if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) {
189 189
 				return new Encoding(['storage' => $storage]);
190 190
 			}
@@ -192,12 +192,12 @@  discard block
 block discarded – undo
192 192
 		});
193 193
 
194 194
 		$quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false);
195
-		Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
195
+		Filesystem::addStorageWrapper('oc_quota', function($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
196 196
 			// set up quota for home storages, even for other users
197 197
 			// which can happen when using sharing
198 198
 			if ($mount instanceof HomeMountPoint) {
199 199
 				$user = $mount->getUser();
200
-				return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
200
+				return new Quota(['storage' => $storage, 'quotaCallback' => function() use ($user) {
201 201
 					return $user->getQuotaBytes();
202 202
 				}, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
203 203
 			}
@@ -205,7 +205,7 @@  discard block
 block discarded – undo
205 205
 			return $storage;
206 206
 		});
207 207
 
208
-		Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
208
+		Filesystem::addStorageWrapper('readonly', function($mountPoint, IStorage $storage, IMountPoint $mount) {
209 209
 			/*
210 210
 			 * Do not allow any operations that modify the storage
211 211
 			 */
@@ -239,8 +239,8 @@  discard block
 block discarded – undo
239 239
 		$this->setupUserMountProviders[$user->getUID()] ??= [];
240 240
 		$previouslySetupProviders = $this->setupUserMountProviders[$user->getUID()];
241 241
 
242
-		$this->setupForUserWith($user, function () use ($user) {
243
-			$this->mountProviderCollection->addMountForUser($user, $this->mountManager, function (
242
+		$this->setupForUserWith($user, function() use ($user) {
243
+			$this->mountProviderCollection->addMountForUser($user, $this->mountManager, function(
244 244
 				string $providerClass,
245 245
 			) use ($user) {
246 246
 				return !in_array($providerClass, $this->setupUserMountProviders[$user->getUID()]);
@@ -275,7 +275,7 @@  discard block
 block discarded – undo
275 275
 
276 276
 		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
277 277
 
278
-		$userDir = '/' . $user->getUID() . '/files';
278
+		$userDir = '/'.$user->getUID().'/files';
279 279
 
280 280
 		Filesystem::initInternal($userDir);
281 281
 
@@ -295,11 +295,11 @@  discard block
 block discarded – undo
295 295
 		} else {
296 296
 			$this->mountManager->addMount(new MountPoint(
297 297
 				new NullStorage([]),
298
-				'/' . $user->getUID()
298
+				'/'.$user->getUID()
299 299
 			));
300 300
 			$this->mountManager->addMount(new MountPoint(
301 301
 				new NullStorage([]),
302
-				'/' . $user->getUID() . '/files'
302
+				'/'.$user->getUID().'/files'
303 303
 			));
304 304
 			$this->setupUsersComplete[] = $user->getUID();
305 305
 		}
@@ -314,12 +314,12 @@  discard block
 block discarded – undo
314 314
 	 */
315 315
 	private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void {
316 316
 		$this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup');
317
-		$userRoot = '/' . $user->getUID() . '/';
317
+		$userRoot = '/'.$user->getUID().'/';
318 318
 		$mounts = $this->mountManager->getAll();
319
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
319
+		$mounts = array_filter($mounts, function(IMountPoint $mount) use ($userRoot) {
320 320
 			return str_starts_with($mount->getMountPoint(), $userRoot);
321 321
 		});
322
-		$allProviders = array_map(function (IMountProvider|IHomeMountProvider|IRootMountProvider $provider) {
322
+		$allProviders = array_map(function(IMountProvider | IHomeMountProvider | IRootMountProvider $provider) {
323 323
 			return get_class($provider);
324 324
 		}, array_merge(
325 325
 			$this->mountProviderCollection->getProviders(),
@@ -327,7 +327,7 @@  discard block
 block discarded – undo
327 327
 			$this->mountProviderCollection->getRootProviders(),
328 328
 		));
329 329
 		$newProviders = array_diff($allProviders, $previouslySetupProviders);
330
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
330
+		$mounts = array_filter($mounts, function(IMountPoint $mount) use ($previouslySetupProviders) {
331 331
 			return !in_array($mount->getMountProvider(), $previouslySetupProviders);
332 332
 		});
333 333
 		$this->registerMounts($user, $mounts, $newProviders);
@@ -362,7 +362,7 @@  discard block
 block discarded – undo
362 362
 		\OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]);
363 363
 		$this->eventLogger->end('fs:setup:user:post-init-mountpoint');
364 364
 
365
-		$userDir = '/' . $user->getUID() . '/files';
365
+		$userDir = '/'.$user->getUID().'/files';
366 366
 		$this->eventLogger->start('fs:setup:user:setup-hook', 'setup legacy hook');
367 367
 		OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]);
368 368
 		$this->eventLogger->end('fs:setup:user:setup-hook');
@@ -406,7 +406,7 @@  discard block
 block discarded – undo
406 406
 			} else {
407 407
 				return null;
408 408
 			}
409
-		} elseif (str_starts_with($path, '/appdata_' . \OC_Util::getInstanceId()) || str_starts_with($path, '/files_external/')) {
409
+		} elseif (str_starts_with($path, '/appdata_'.\OC_Util::getInstanceId()) || str_starts_with($path, '/files_external/')) {
410 410
 			return null;
411 411
 		} else {
412 412
 			[, $userId] = explode('/', $path);
@@ -436,7 +436,7 @@  discard block
 block discarded – undo
436 436
 		}
437 437
 
438 438
 		// for the user's home folder, and includes children we need everything always
439
-		if (rtrim($path) === '/' . $user->getUID() . '/files' && $includeChildren) {
439
+		if (rtrim($path) === '/'.$user->getUID().'/files' && $includeChildren) {
440 440
 			$this->setupForUser($user);
441 441
 			return;
442 442
 		}
@@ -469,7 +469,7 @@  discard block
 block discarded – undo
469 469
 			= $this->isPathSetup($mountPoint);
470 470
 		if (!$isMountProviderSetup && !$isPathSetupAsAuthoritative) {
471 471
 			if ($mountProvider === '') {
472
-				$this->logger->debug('mount at ' . $cachedMount->getMountPoint() . ' has no provider set, performing full setup');
472
+				$this->logger->debug('mount at '.$cachedMount->getMountPoint().' has no provider set, performing full setup');
473 473
 				$this->eventLogger->end('fs:setup:user:path:find');
474 474
 				$this->setupForUser($user);
475 475
 				$this->eventLogger->end('fs:setup:user:path');
@@ -560,7 +560,7 @@  discard block
 block discarded – undo
560 560
 				$this->setupMountProviderPaths[$mountPoint] = self::SETUP_WITH_CHILDREN;
561 561
 				foreach ($authoritativeCachedMounts as $providerClass => $cachedMounts) {
562 562
 					$providerArgs = array_filter(array_map(
563
-						static function (ICachedMountInfo $info) use ($rootsMetadata) {
563
+						static function(ICachedMountInfo $info) use ($rootsMetadata) {
564 564
 							$rootMetadata = $rootsMetadata[$info->getRootId()] ?? null;
565 565
 
566 566
 							return $rootMetadata
@@ -589,7 +589,7 @@  discard block
 block discarded – undo
589 589
 				$this->registerMounts($user, $fullProviderMounts, $currentProviders);
590 590
 			}
591 591
 
592
-			$this->setupForUserWith($user, function () use ($fullProviderMounts, $authoritativeMounts) {
592
+			$this->setupForUserWith($user, function() use ($fullProviderMounts, $authoritativeMounts) {
593 593
 				$allMounts = [...$fullProviderMounts, ...$authoritativeMounts];
594 594
 				array_walk($allMounts, $this->mountManager->addMount(...));
595 595
 			});
@@ -633,12 +633,12 @@  discard block
 block discarded – undo
633 633
 			return;
634 634
 		}
635 635
 
636
-		$this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
636
+		$this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for '.implode(', ', $providers));
637 637
 
638 638
 		$this->oneTimeUserSetup($user);
639 639
 
640 640
 		// home providers are always used
641
-		$providers = array_filter($providers, function (string $provider) {
641
+		$providers = array_filter($providers, function(string $provider) {
642 642
 			return !is_subclass_of($provider, IHomeMountProvider::class);
643 643
 		});
644 644
 
@@ -661,7 +661,7 @@  discard block
 block discarded – undo
661 661
 		}
662 662
 
663 663
 		$this->registerMounts($user, $mounts, $providers);
664
-		$this->setupForUserWith($user, function () use ($mounts) {
664
+		$this->setupForUserWith($user, function() use ($mounts) {
665 665
 			array_walk($mounts, [$this->mountManager, 'addMount']);
666 666
 		});
667 667
 		$this->eventLogger->end('fs:setup:user:providers');
@@ -684,7 +684,7 @@  discard block
 block discarded – undo
684 684
 	private function listenForNewMountProviders() {
685 685
 		if (!$this->listeningForProviders) {
686 686
 			$this->listeningForProviders = true;
687
-			$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
687
+			$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function(
688 688
 				IMountProvider $provider,
689 689
 			) {
690 690
 				foreach ($this->setupUsers as $userId) {
@@ -702,16 +702,16 @@  discard block
 block discarded – undo
702 702
 		// note that this event handling is intentionally pessimistic
703 703
 		// clearing the cache to often is better than not enough
704 704
 
705
-		$this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
705
+		$this->eventDispatcher->addListener(UserAddedEvent::class, function(UserAddedEvent $event) {
706 706
 			$this->cache->remove($event->getUser()->getUID());
707 707
 		});
708
-		$this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
708
+		$this->eventDispatcher->addListener(UserRemovedEvent::class, function(UserRemovedEvent $event) {
709 709
 			$this->cache->remove($event->getUser()->getUID());
710 710
 		});
711
-		$this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
711
+		$this->eventDispatcher->addListener(ShareCreatedEvent::class, function(ShareCreatedEvent $event) {
712 712
 			$this->cache->remove($event->getShare()->getSharedWith());
713 713
 		});
714
-		$this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
714
+		$this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function(InvalidateMountCacheEvent $event,
715 715
 		) {
716 716
 			if ($user = $event->getUser()) {
717 717
 				$this->cache->remove($user->getUID());
@@ -728,7 +728,7 @@  discard block
 block discarded – undo
728 728
 		];
729 729
 
730 730
 		foreach ($genericEvents as $genericEvent) {
731
-			$this->eventDispatcher->addListener($genericEvent, function ($event) {
731
+			$this->eventDispatcher->addListener($genericEvent, function($event) {
732 732
 				$this->cache->clear();
733 733
 			});
734 734
 		}
Please login to merge, or discard this patch.
tests/lib/Files/SetupManagerTest.php 2 patches
Indentation   +453 added lines, -453 removed lines patch added patch discarded remove patch
@@ -37,464 +37,464 @@
 block discarded – undo
37 37
 
38 38
 class SetupManagerTest extends TestCase {
39 39
 
40
-	/**
41
-	 * @var (object&\PHPUnit\Framework\MockObject\MockObject)|IUserManager|(IUserManager&object&\PHPUnit\Framework\MockObject\MockObject)|(IUserManager&\PHPUnit\Framework\MockObject\MockObject)|\PHPUnit\Framework\MockObject\MockObject
42
-	 */
43
-	private IUserManager&MockObject $userManager;
44
-	private IUserMountCache&MockObject $userMountCache;
45
-	private ICache&MockObject $cache;
46
-	private FileAccess&MockObject $fileAccess;
47
-	private MountProviderCollection&MockObject $mountProviderCollection;
48
-	private IMountManager&MockObject $mountManager;
49
-	private SetupManager $setupManager;
50
-	private IUser&MockObject $user;
51
-	private string $userId;
52
-	private string $path;
53
-	private string $mountPoint;
54
-
55
-	protected function setUp(): void {
56
-		$eventLogger = $this->createMock(IEventLogger::class);
57
-		$eventLogger->method('start');
58
-		$eventLogger->method('end');
59
-
60
-		$this->userManager = $this->createMock(IUserManager::class);
61
-		$this->cache = $this->createMock(ICache::class);
62
-
63
-		$this->userId = 'alice';
64
-		$this->path = "/{$this->userId}/files/folder";
65
-		$this->mountPoint = "{$this->path}/";
66
-
67
-		$this->user = $this->createMock(IUser::class);
68
-		$this->user->method('getUID')->willReturn($this->userId);
69
-		$this->userManager->method('get')
70
-			->with($this->userId)
71
-			->willReturn($this->user);
72
-
73
-		// avoid triggering full setup required check
74
-		$this->cache->method('get')
75
-			->with($this->userId)
76
-			->willReturn(true);
77
-
78
-		$this->mountProviderCollection = $this->createMock(MountProviderCollection::class);
79
-		$this->mountManager = $this->createMock(IMountManager::class);
80
-		$eventDispatcher = $this->createMock(IEventDispatcher::class);
81
-		$eventDispatcher->method('addListener');
82
-		$this->userMountCache = $this->createMock(IUserMountCache::class);
83
-		$lockdownManager = $this->createMock(ILockdownManager::class);
84
-		$userSession = $this->createMock(IUserSession::class);
85
-		$cacheFactory = $this->createMock(ICacheFactory::class);
86
-		$cacheFactory->expects($this->once())
87
-			->method('createDistributed')
88
-			->with('setupmanager::')
89
-			->willReturn($this->cache);
90
-		$logger = $this->createMock(LoggerInterface::class);
91
-		$config = $this->createMock(IConfig::class);
92
-		$config->method('getSystemValueBool')->willReturn(false);
93
-		$shareDisableChecker = $this->createMock(ShareDisableChecker::class);
94
-		$appManager = $this->createMock(IAppManager::class);
95
-		$this->fileAccess = $this->createMock(FileAccess::class);
96
-
97
-		$lockdownManager->method('canAccessFilesystem')->willReturn(true);
98
-
99
-		$this->setupManager = new SetupManager(
100
-			$eventLogger,
101
-			$this->mountProviderCollection,
102
-			$this->mountManager,
103
-			$this->userManager,
104
-			$eventDispatcher,
105
-			$this->userMountCache,
106
-			$lockdownManager,
107
-			$userSession,
108
-			$cacheFactory,
109
-			$logger,
110
-			$config,
111
-			$shareDisableChecker,
112
-			$appManager,
113
-			$this->fileAccess,
114
-		);
115
-	}
116
-
117
-	public function testTearDown(): void {
118
-		$this->setupManager->tearDown();
119
-	}
120
-
121
-	public function testSetupForPathWithPartialProviderSkipsAlreadySetupPath(): void {
122
-		$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
123
-
124
-		$this->userMountCache->expects($this->exactly(2))
125
-			->method('getMountForPath')
126
-			->with($this->user, $this->path)
127
-			->willReturn($cachedMount);
128
-		$this->userMountCache->expects($this->never())->method('registerMounts');
129
-		$this->userMountCache->expects($this->never())->method('getMountsInPath');
130
-
131
-		$this->fileAccess->expects($this->once())
132
-			->method('getByFileId')
133
-			->with(42)
134
-			->willReturn($this->createMock(CacheEntry::class));
135
-
136
-		$partialMount = $this->createMock(IMountPoint::class);
137
-
138
-		$this->mountProviderCollection->expects($this->once())
139
-			->method('getUserMountsFromProviderByPath')
140
-			->with(
141
-				SetupManagerTestPartialMountProvider::class,
142
-				$this->path,
143
-				$this->callback(function (array $args) use ($cachedMount) {
144
-					$this->assertCount(1, $args);
145
-					$this->assertInstanceOf(IMountProviderArgs::class, $args[0]);
146
-					$this->assertSame($cachedMount, $args[0]->mountInfo);
147
-					return true;
148
-				})
149
-			)
150
-			->willReturn([$partialMount]);
151
-
152
-		$homeMount = $this->createMock(IMountPoint::class);
153
-
154
-		$this->mountProviderCollection->expects($this->once())
155
-			->method('getHomeMountForUser')
156
-			->willReturn($homeMount);
157
-
158
-		$this->mountProviderCollection->expects($this->never())
159
-			->method('getUserMountsForProviderClasses');
160
-
161
-		$invokedCount = $this->exactly(2);
162
-		$addMountExpectations = [
163
-			1 => $homeMount,
164
-			2 => $partialMount,
165
-		];
166
-		$this->mountManager->expects($invokedCount)
167
-			->method('addMount')
168
-			->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
169
-
170
-		// setup called twice, provider should only be called once
171
-		$this->setupManager->setupForPath($this->path, false);
172
-		$this->setupManager->setupForPath($this->path, false);
173
-	}
174
-
175
-	public function testSetupForPathWithNonPartialProviderSkipsAlreadySetupProvider(): void {
176
-		$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42,
177
-			IMountProvider::class);
178
-
179
-		$this->userMountCache->expects($this->exactly(2))
180
-			->method('getMountForPath')
181
-			->with($this->user, $this->path)
182
-			->willReturn($cachedMount);
183
-
184
-		$this->userMountCache->expects($this->once())->method('registerMounts');
185
-		$this->userMountCache->expects($this->never())->method('getMountsInPath');
186
-
187
-		$providerMount = $this->createMock(IMountPoint::class);
188
-
189
-		$this->mountProviderCollection->expects($this->once())
190
-			->method('getUserMountsForProviderClasses')
191
-			->with($this->user, [IMountProvider::class])
192
-			->willReturn([$providerMount]);
193
-
194
-		$homeMount = $this->createMock(IMountPoint::class);
195
-
196
-		$this->mountProviderCollection->expects($this->once())
197
-			->method('getHomeMountForUser')
198
-			->willReturn($homeMount);
199
-
200
-		$invokedCount = $this->exactly(2);
201
-		$addMountExpectations = [
202
-			1 => $homeMount,
203
-			2 => $providerMount,
204
-		];
205
-		$this->mountManager->expects($invokedCount)
206
-			->method('addMount')
207
-			->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
208
-
209
-		// setup called twice, provider should only be called once
210
-		$this->setupManager->setupForPath($this->path, false);
211
-		$this->setupManager->setupForPath($this->path, false);
212
-	}
213
-
214
-	public function testSetupForPathWithChildrenAndNonPartialProviderSkipsAlreadySetupProvider(): void {
215
-		$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42, IMountProvider::class);
216
-		$additionalCachedMount = $this->getCachedMountInfo($this->mountPoint . 'additional/', 43, SetupManagerTestFullMountProvider::class);
217
-
218
-		$this->userMountCache->expects($this->exactly(2))
219
-			->method('getMountForPath')
220
-			->with($this->user, $this->path)
221
-			->willReturn($cachedMount);
222
-
223
-		$this->userMountCache->expects($this->once())->method('registerMounts');
224
-		$this->userMountCache->expects($this->once())->method('getMountsInPath')
225
-			->willReturn([$additionalCachedMount]);
226
-
227
-		$mount = $this->createMock(IMountPoint::class);
228
-		$additionalMount = $this->createMock(IMountPoint::class);
229
-
230
-		$invokedCount = $this->exactly(2);
231
-		$this->mountProviderCollection->expects($invokedCount)
232
-			->method('getUserMountsForProviderClasses')
233
-			->willReturnCallback(function (IUser $userArg, array $providersArg) use (
234
-				$additionalMount,
235
-				$mount,
236
-				$invokedCount) {
237
-				if ($invokedCount->numberOfInvocations() === 1) {
238
-					$providers = [IMountProvider::class];
239
-					$returnMounts = [$mount];
240
-				} else {
241
-					$providers = [SetupManagerTestFullMountProvider::class];
242
-					$returnMounts = [$additionalMount];
243
-				}
244
-
245
-				$this->assertSame($this->user, $userArg);
246
-				$this->assertSame($providersArg, $providers);
247
-
248
-				return $returnMounts;
249
-			});
250
-
251
-		$homeMount = $this->createMock(IMountPoint::class);
252
-
253
-		$this->mountProviderCollection->expects($this->once())
254
-			->method('getHomeMountForUser')
255
-			->willReturn($homeMount);
256
-
257
-		$invokedCount = $this->exactly(3);
258
-		$addMountExpectations = [
259
-			1 => $homeMount,
260
-			2 => $mount,
261
-			3 => $additionalMount,
262
-		];
263
-		$this->mountManager->expects($invokedCount)
264
-			->method('addMount')
265
-			->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
266
-
267
-		// setup called twice, provider should only be called once
268
-		$this->setupManager->setupForPath($this->path, true);
269
-		$this->setupManager->setupForPath($this->path, false);
270
-	}
271
-
272
-	public function testSetupForPathWithChildrenAndPartialProviderSkipsIfParentAlreadySetup():	void {
273
-		$childPath = "{$this->path}/child";
274
-		$childMountPoint = "{$childPath}/";
275
-
276
-		$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
277
-		$cachedChildMount = $this->getCachedMountInfo($childMountPoint, 43);
278
-
279
-		$invokedCount = $this->exactly(3);
280
-		$this->userMountCache->expects($invokedCount)
281
-			->method('getMountForPath')
282
-			->willReturnCallback(function (IUser $userArg, string $pathArg) use (
283
-				$cachedChildMount,
284
-				$cachedMount,
285
-				$childPath,
286
-				$invokedCount) {
287
-				if ($invokedCount->numberOfInvocations() === 1) {
288
-					$expectedPath = $this->path;
289
-					$returnMount = $cachedMount;
290
-				} else {
291
-					$expectedPath = $childPath;
292
-					$returnMount = $cachedChildMount;
293
-				}
294
-
295
-				$this->assertSame($this->user, $userArg);
296
-				$this->assertSame($expectedPath, $pathArg);
297
-
298
-				return $returnMount;
299
-			});
300
-
301
-		$this->userMountCache->expects($this->never())->method('registerMounts');
302
-		$this->userMountCache->expects($this->exactly(2))
303
-			->method('getMountsInPath')
304
-			->willReturn([$cachedChildMount]);
305
-
306
-		$this->fileAccess->expects($this->once())
307
-			->method('getByFileId')
308
-			->with(42)
309
-			->willReturn($this->createMock(CacheEntry::class));
310
-
311
-		$this->fileAccess->expects($this->once())
312
-			->method('getByFileIds')
313
-			->with([43])
314
-			->willReturn([43 => $this->createMock(CacheEntry::class)]);
315
-
316
-		$partialMount = $this->createMock(IMountPoint::class);
317
-		$partialChildMount = $this->createMock(IMountPoint::class);
318
-
319
-		$invokedCount = $this->exactly(2);
320
-		$this->mountProviderCollection->expects($invokedCount)
321
-			->method('getUserMountsFromProviderByPath')
322
-			->willReturnCallback(function (
323
-				string $providerClass,
324
-				string $pathArg,
325
-				array $mountProviderArgs,
326
-			) use (
327
-				$cachedChildMount,
328
-				$partialMount,
329
-				$partialChildMount,
330
-				$cachedMount,
331
-				$invokedCount
332
-			) {
333
-				$expectedPath = $this->path;
334
-				if ($invokedCount->numberOfInvocations() === 1) {
335
-					// call for the parent
336
-					$expectedCachedMount = $cachedMount;
337
-					$mountPoints = [$partialMount];
338
-				} else {
339
-					// call for the children
340
-					$expectedCachedMount = $cachedChildMount;
341
-					$mountPoints = [$partialChildMount];
342
-				}
343
-
344
-				$this->assertSame(SetupManagerTestPartialMountProvider::class, $providerClass);
345
-				$this->assertSame($expectedPath, $pathArg);
346
-				$this->assertCount(1, $mountProviderArgs);
347
-				$this->assertInstanceOf(IMountProviderArgs::class, $mountProviderArgs[0]);
348
-				$this->assertSame($expectedCachedMount, $mountProviderArgs[0]->mountInfo);
349
-
350
-				return $mountPoints;
351
-			});
352
-
353
-		$homeMount = $this->createMock(IMountPoint::class);
354
-
355
-		$this->mountProviderCollection->expects($this->once())
356
-			->method('getHomeMountForUser')
357
-			->willReturn($homeMount);
358
-
359
-		$this->mountProviderCollection->expects($this->never())
360
-			->method('getUserMountsForProviderClasses');
361
-
362
-		$invokedCount = $this->exactly(3);
363
-		$addMountExpectations = [
364
-			1 => $homeMount,
365
-			2 => $partialMount,
366
-			3 => $partialChildMount,
367
-		];
368
-		$this->mountManager->expects($invokedCount)
369
-			->method('addMount')
370
-			->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
371
-
372
-		// once the setup for a path has been done with children, setup for sub
373
-		// paths should not create the same new mounts again
374
-		$this->setupManager->setupForPath($this->path, true);
375
-		$this->setupManager->setupForPath($childPath, false);
376
-		$this->setupManager->setupForPath($childPath, true);
377
-	}
378
-
379
-	public function testSetupForPathHandlesPartialAndFullProvidersWithChildren(): void {
380
-		$parentPartialCachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
381
-		$childCachedPartialMount = $this->getCachedMountInfo("{$this->mountPoint}partial/", 43);
382
-		$childCachedFullMount = $this->getCachedMountInfo("{$this->mountPoint}full/", 44, SetupManagerTestFullMountProvider::class);
383
-
384
-		$this->userMountCache->expects($this->exactly(2))
385
-			->method('getMountForPath')
386
-			->with($this->user, $this->path)
387
-			->willReturn($parentPartialCachedMount);
388
-		$this->userMountCache->expects($this->exactly(2))
389
-			->method('getMountsInPath')
390
-			->with($this->user, $this->path)
391
-			->willReturn([$childCachedPartialMount, $childCachedFullMount]);
392
-
393
-		$homeMount = $this->createMock(IMountPoint::class);
394
-		$parentPartialMount = $this->createMock(IMountPoint::class);
395
-		$childPartialMount = $this->createMock(IMountPoint::class);
396
-		$childFullProviderMount = $this->createMock(IMountPoint::class);
397
-
398
-		$this->mountProviderCollection->expects($this->once())
399
-			->method('getHomeMountForUser')
400
-			->willReturn($homeMount);
401
-
402
-		$this->userMountCache->expects($this->once())
403
-			->method('registerMounts')
404
-			->with(
405
-				$this->user, [$childFullProviderMount],
406
-				[SetupManagerTestFullMountProvider::class],
407
-			);
408
-
409
-		$this->fileAccess->expects($this->once())
410
-			->method('getByFileId')
411
-			->with(42)
412
-			->willReturn($this->createMock(CacheEntry::class));
413
-		$childMetadata = $this->createMock(CacheEntry::class);
414
-		$this->fileAccess->expects($this->once())
415
-			->method('getByFileIds')
416
-			->with([43])
417
-			->willReturn([43 => $childMetadata]);
418
-
419
-		$invokedCount = $this->exactly(2);
420
-		$this->mountProviderCollection->expects($invokedCount)
421
-			->method('getUserMountsFromProviderByPath')
422
-			->willReturnCallback(function (string $providerClass, string $pathArg, array $mountProviderArgs) use (
423
-				$childCachedPartialMount,
424
-				$childPartialMount,
425
-				$parentPartialMount,
426
-				$parentPartialCachedMount,
427
-				$invokedCount) {
428
-				$expectedPath = $this->path;
429
-				if ($invokedCount->numberOfInvocations() === 1) {
430
-					// call for the parent
431
-					$expectedCachedMount = $parentPartialCachedMount;
432
-					$mountPoints = [$parentPartialMount];
433
-				} else {
434
-					// call for the children
435
-					$expectedCachedMount = $childCachedPartialMount;
436
-					$mountPoints = [$childPartialMount];
437
-				}
438
-
439
-				$this->assertSame(SetupManagerTestPartialMountProvider::class, $providerClass);
440
-				$this->assertSame($expectedPath, $pathArg);
441
-				$this->assertCount(1, $mountProviderArgs);
442
-				$this->assertInstanceOf(IMountProviderArgs::class, $mountProviderArgs[0]);
443
-				$this->assertSame($expectedCachedMount, $mountProviderArgs[0]->mountInfo);
444
-
445
-				return $mountPoints;
446
-			});
447
-
448
-		$this->mountProviderCollection->expects($this->once())
449
-			->method('getUserMountsForProviderClasses')
450
-			->with($this->user, [SetupManagerTestFullMountProvider::class])
451
-			->willReturn([$childFullProviderMount]);
452
-
453
-		$invokedCount = $this->exactly(4);
454
-		$addMountExpectations = [
455
-			1 => $homeMount,
456
-			2 => $childFullProviderMount,
457
-			3 => $parentPartialMount,
458
-			4 => $childPartialMount,
459
-		];
460
-		$this->mountManager->expects($invokedCount)
461
-			->method('addMount')
462
-			->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
463
-
464
-		// call twice to test that providers and mounts are only called once
465
-		$this->setupManager->setupForPath($this->path, true);
466
-		$this->setupManager->setupForPath($this->path, true);
467
-	}
468
-
469
-	private function getAddMountCheckCallback(InvokedCount $invokedCount, $expectations): \Closure {
470
-		return function (IMountPoint $actualMount) use ($invokedCount, $expectations) {
471
-			$expectedMount = $expectations[$invokedCount->numberOfInvocations()] ?? null;
472
-			$this->assertSame($expectedMount, $actualMount);
473
-		};
474
-	}
475
-
476
-	public function getCachedMountInfo(string $mountPoint, int $rootId, string $providerClass = SetupManagerTestPartialMountProvider::class): ICachedMountInfo&MockObject {
477
-		$cachedMount = $this->createMock(ICachedMountInfo::class);
478
-		$cachedMount->method('getMountProvider')->willReturn($providerClass);
479
-		$cachedMount->method('getMountPoint')->willReturn($mountPoint);
480
-		$cachedMount->method('getRootId')->willReturn($rootId);
481
-
482
-		return $cachedMount;
483
-	}
40
+    /**
41
+     * @var (object&\PHPUnit\Framework\MockObject\MockObject)|IUserManager|(IUserManager&object&\PHPUnit\Framework\MockObject\MockObject)|(IUserManager&\PHPUnit\Framework\MockObject\MockObject)|\PHPUnit\Framework\MockObject\MockObject
42
+     */
43
+    private IUserManager&MockObject $userManager;
44
+    private IUserMountCache&MockObject $userMountCache;
45
+    private ICache&MockObject $cache;
46
+    private FileAccess&MockObject $fileAccess;
47
+    private MountProviderCollection&MockObject $mountProviderCollection;
48
+    private IMountManager&MockObject $mountManager;
49
+    private SetupManager $setupManager;
50
+    private IUser&MockObject $user;
51
+    private string $userId;
52
+    private string $path;
53
+    private string $mountPoint;
54
+
55
+    protected function setUp(): void {
56
+        $eventLogger = $this->createMock(IEventLogger::class);
57
+        $eventLogger->method('start');
58
+        $eventLogger->method('end');
59
+
60
+        $this->userManager = $this->createMock(IUserManager::class);
61
+        $this->cache = $this->createMock(ICache::class);
62
+
63
+        $this->userId = 'alice';
64
+        $this->path = "/{$this->userId}/files/folder";
65
+        $this->mountPoint = "{$this->path}/";
66
+
67
+        $this->user = $this->createMock(IUser::class);
68
+        $this->user->method('getUID')->willReturn($this->userId);
69
+        $this->userManager->method('get')
70
+            ->with($this->userId)
71
+            ->willReturn($this->user);
72
+
73
+        // avoid triggering full setup required check
74
+        $this->cache->method('get')
75
+            ->with($this->userId)
76
+            ->willReturn(true);
77
+
78
+        $this->mountProviderCollection = $this->createMock(MountProviderCollection::class);
79
+        $this->mountManager = $this->createMock(IMountManager::class);
80
+        $eventDispatcher = $this->createMock(IEventDispatcher::class);
81
+        $eventDispatcher->method('addListener');
82
+        $this->userMountCache = $this->createMock(IUserMountCache::class);
83
+        $lockdownManager = $this->createMock(ILockdownManager::class);
84
+        $userSession = $this->createMock(IUserSession::class);
85
+        $cacheFactory = $this->createMock(ICacheFactory::class);
86
+        $cacheFactory->expects($this->once())
87
+            ->method('createDistributed')
88
+            ->with('setupmanager::')
89
+            ->willReturn($this->cache);
90
+        $logger = $this->createMock(LoggerInterface::class);
91
+        $config = $this->createMock(IConfig::class);
92
+        $config->method('getSystemValueBool')->willReturn(false);
93
+        $shareDisableChecker = $this->createMock(ShareDisableChecker::class);
94
+        $appManager = $this->createMock(IAppManager::class);
95
+        $this->fileAccess = $this->createMock(FileAccess::class);
96
+
97
+        $lockdownManager->method('canAccessFilesystem')->willReturn(true);
98
+
99
+        $this->setupManager = new SetupManager(
100
+            $eventLogger,
101
+            $this->mountProviderCollection,
102
+            $this->mountManager,
103
+            $this->userManager,
104
+            $eventDispatcher,
105
+            $this->userMountCache,
106
+            $lockdownManager,
107
+            $userSession,
108
+            $cacheFactory,
109
+            $logger,
110
+            $config,
111
+            $shareDisableChecker,
112
+            $appManager,
113
+            $this->fileAccess,
114
+        );
115
+    }
116
+
117
+    public function testTearDown(): void {
118
+        $this->setupManager->tearDown();
119
+    }
120
+
121
+    public function testSetupForPathWithPartialProviderSkipsAlreadySetupPath(): void {
122
+        $cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
123
+
124
+        $this->userMountCache->expects($this->exactly(2))
125
+            ->method('getMountForPath')
126
+            ->with($this->user, $this->path)
127
+            ->willReturn($cachedMount);
128
+        $this->userMountCache->expects($this->never())->method('registerMounts');
129
+        $this->userMountCache->expects($this->never())->method('getMountsInPath');
130
+
131
+        $this->fileAccess->expects($this->once())
132
+            ->method('getByFileId')
133
+            ->with(42)
134
+            ->willReturn($this->createMock(CacheEntry::class));
135
+
136
+        $partialMount = $this->createMock(IMountPoint::class);
137
+
138
+        $this->mountProviderCollection->expects($this->once())
139
+            ->method('getUserMountsFromProviderByPath')
140
+            ->with(
141
+                SetupManagerTestPartialMountProvider::class,
142
+                $this->path,
143
+                $this->callback(function (array $args) use ($cachedMount) {
144
+                    $this->assertCount(1, $args);
145
+                    $this->assertInstanceOf(IMountProviderArgs::class, $args[0]);
146
+                    $this->assertSame($cachedMount, $args[0]->mountInfo);
147
+                    return true;
148
+                })
149
+            )
150
+            ->willReturn([$partialMount]);
151
+
152
+        $homeMount = $this->createMock(IMountPoint::class);
153
+
154
+        $this->mountProviderCollection->expects($this->once())
155
+            ->method('getHomeMountForUser')
156
+            ->willReturn($homeMount);
157
+
158
+        $this->mountProviderCollection->expects($this->never())
159
+            ->method('getUserMountsForProviderClasses');
160
+
161
+        $invokedCount = $this->exactly(2);
162
+        $addMountExpectations = [
163
+            1 => $homeMount,
164
+            2 => $partialMount,
165
+        ];
166
+        $this->mountManager->expects($invokedCount)
167
+            ->method('addMount')
168
+            ->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
169
+
170
+        // setup called twice, provider should only be called once
171
+        $this->setupManager->setupForPath($this->path, false);
172
+        $this->setupManager->setupForPath($this->path, false);
173
+    }
174
+
175
+    public function testSetupForPathWithNonPartialProviderSkipsAlreadySetupProvider(): void {
176
+        $cachedMount = $this->getCachedMountInfo($this->mountPoint, 42,
177
+            IMountProvider::class);
178
+
179
+        $this->userMountCache->expects($this->exactly(2))
180
+            ->method('getMountForPath')
181
+            ->with($this->user, $this->path)
182
+            ->willReturn($cachedMount);
183
+
184
+        $this->userMountCache->expects($this->once())->method('registerMounts');
185
+        $this->userMountCache->expects($this->never())->method('getMountsInPath');
186
+
187
+        $providerMount = $this->createMock(IMountPoint::class);
188
+
189
+        $this->mountProviderCollection->expects($this->once())
190
+            ->method('getUserMountsForProviderClasses')
191
+            ->with($this->user, [IMountProvider::class])
192
+            ->willReturn([$providerMount]);
193
+
194
+        $homeMount = $this->createMock(IMountPoint::class);
195
+
196
+        $this->mountProviderCollection->expects($this->once())
197
+            ->method('getHomeMountForUser')
198
+            ->willReturn($homeMount);
199
+
200
+        $invokedCount = $this->exactly(2);
201
+        $addMountExpectations = [
202
+            1 => $homeMount,
203
+            2 => $providerMount,
204
+        ];
205
+        $this->mountManager->expects($invokedCount)
206
+            ->method('addMount')
207
+            ->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
208
+
209
+        // setup called twice, provider should only be called once
210
+        $this->setupManager->setupForPath($this->path, false);
211
+        $this->setupManager->setupForPath($this->path, false);
212
+    }
213
+
214
+    public function testSetupForPathWithChildrenAndNonPartialProviderSkipsAlreadySetupProvider(): void {
215
+        $cachedMount = $this->getCachedMountInfo($this->mountPoint, 42, IMountProvider::class);
216
+        $additionalCachedMount = $this->getCachedMountInfo($this->mountPoint . 'additional/', 43, SetupManagerTestFullMountProvider::class);
217
+
218
+        $this->userMountCache->expects($this->exactly(2))
219
+            ->method('getMountForPath')
220
+            ->with($this->user, $this->path)
221
+            ->willReturn($cachedMount);
222
+
223
+        $this->userMountCache->expects($this->once())->method('registerMounts');
224
+        $this->userMountCache->expects($this->once())->method('getMountsInPath')
225
+            ->willReturn([$additionalCachedMount]);
226
+
227
+        $mount = $this->createMock(IMountPoint::class);
228
+        $additionalMount = $this->createMock(IMountPoint::class);
229
+
230
+        $invokedCount = $this->exactly(2);
231
+        $this->mountProviderCollection->expects($invokedCount)
232
+            ->method('getUserMountsForProviderClasses')
233
+            ->willReturnCallback(function (IUser $userArg, array $providersArg) use (
234
+                $additionalMount,
235
+                $mount,
236
+                $invokedCount) {
237
+                if ($invokedCount->numberOfInvocations() === 1) {
238
+                    $providers = [IMountProvider::class];
239
+                    $returnMounts = [$mount];
240
+                } else {
241
+                    $providers = [SetupManagerTestFullMountProvider::class];
242
+                    $returnMounts = [$additionalMount];
243
+                }
244
+
245
+                $this->assertSame($this->user, $userArg);
246
+                $this->assertSame($providersArg, $providers);
247
+
248
+                return $returnMounts;
249
+            });
250
+
251
+        $homeMount = $this->createMock(IMountPoint::class);
252
+
253
+        $this->mountProviderCollection->expects($this->once())
254
+            ->method('getHomeMountForUser')
255
+            ->willReturn($homeMount);
256
+
257
+        $invokedCount = $this->exactly(3);
258
+        $addMountExpectations = [
259
+            1 => $homeMount,
260
+            2 => $mount,
261
+            3 => $additionalMount,
262
+        ];
263
+        $this->mountManager->expects($invokedCount)
264
+            ->method('addMount')
265
+            ->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
266
+
267
+        // setup called twice, provider should only be called once
268
+        $this->setupManager->setupForPath($this->path, true);
269
+        $this->setupManager->setupForPath($this->path, false);
270
+    }
271
+
272
+    public function testSetupForPathWithChildrenAndPartialProviderSkipsIfParentAlreadySetup():	void {
273
+        $childPath = "{$this->path}/child";
274
+        $childMountPoint = "{$childPath}/";
275
+
276
+        $cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
277
+        $cachedChildMount = $this->getCachedMountInfo($childMountPoint, 43);
278
+
279
+        $invokedCount = $this->exactly(3);
280
+        $this->userMountCache->expects($invokedCount)
281
+            ->method('getMountForPath')
282
+            ->willReturnCallback(function (IUser $userArg, string $pathArg) use (
283
+                $cachedChildMount,
284
+                $cachedMount,
285
+                $childPath,
286
+                $invokedCount) {
287
+                if ($invokedCount->numberOfInvocations() === 1) {
288
+                    $expectedPath = $this->path;
289
+                    $returnMount = $cachedMount;
290
+                } else {
291
+                    $expectedPath = $childPath;
292
+                    $returnMount = $cachedChildMount;
293
+                }
294
+
295
+                $this->assertSame($this->user, $userArg);
296
+                $this->assertSame($expectedPath, $pathArg);
297
+
298
+                return $returnMount;
299
+            });
300
+
301
+        $this->userMountCache->expects($this->never())->method('registerMounts');
302
+        $this->userMountCache->expects($this->exactly(2))
303
+            ->method('getMountsInPath')
304
+            ->willReturn([$cachedChildMount]);
305
+
306
+        $this->fileAccess->expects($this->once())
307
+            ->method('getByFileId')
308
+            ->with(42)
309
+            ->willReturn($this->createMock(CacheEntry::class));
310
+
311
+        $this->fileAccess->expects($this->once())
312
+            ->method('getByFileIds')
313
+            ->with([43])
314
+            ->willReturn([43 => $this->createMock(CacheEntry::class)]);
315
+
316
+        $partialMount = $this->createMock(IMountPoint::class);
317
+        $partialChildMount = $this->createMock(IMountPoint::class);
318
+
319
+        $invokedCount = $this->exactly(2);
320
+        $this->mountProviderCollection->expects($invokedCount)
321
+            ->method('getUserMountsFromProviderByPath')
322
+            ->willReturnCallback(function (
323
+                string $providerClass,
324
+                string $pathArg,
325
+                array $mountProviderArgs,
326
+            ) use (
327
+                $cachedChildMount,
328
+                $partialMount,
329
+                $partialChildMount,
330
+                $cachedMount,
331
+                $invokedCount
332
+            ) {
333
+                $expectedPath = $this->path;
334
+                if ($invokedCount->numberOfInvocations() === 1) {
335
+                    // call for the parent
336
+                    $expectedCachedMount = $cachedMount;
337
+                    $mountPoints = [$partialMount];
338
+                } else {
339
+                    // call for the children
340
+                    $expectedCachedMount = $cachedChildMount;
341
+                    $mountPoints = [$partialChildMount];
342
+                }
343
+
344
+                $this->assertSame(SetupManagerTestPartialMountProvider::class, $providerClass);
345
+                $this->assertSame($expectedPath, $pathArg);
346
+                $this->assertCount(1, $mountProviderArgs);
347
+                $this->assertInstanceOf(IMountProviderArgs::class, $mountProviderArgs[0]);
348
+                $this->assertSame($expectedCachedMount, $mountProviderArgs[0]->mountInfo);
349
+
350
+                return $mountPoints;
351
+            });
352
+
353
+        $homeMount = $this->createMock(IMountPoint::class);
354
+
355
+        $this->mountProviderCollection->expects($this->once())
356
+            ->method('getHomeMountForUser')
357
+            ->willReturn($homeMount);
358
+
359
+        $this->mountProviderCollection->expects($this->never())
360
+            ->method('getUserMountsForProviderClasses');
361
+
362
+        $invokedCount = $this->exactly(3);
363
+        $addMountExpectations = [
364
+            1 => $homeMount,
365
+            2 => $partialMount,
366
+            3 => $partialChildMount,
367
+        ];
368
+        $this->mountManager->expects($invokedCount)
369
+            ->method('addMount')
370
+            ->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
371
+
372
+        // once the setup for a path has been done with children, setup for sub
373
+        // paths should not create the same new mounts again
374
+        $this->setupManager->setupForPath($this->path, true);
375
+        $this->setupManager->setupForPath($childPath, false);
376
+        $this->setupManager->setupForPath($childPath, true);
377
+    }
378
+
379
+    public function testSetupForPathHandlesPartialAndFullProvidersWithChildren(): void {
380
+        $parentPartialCachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
381
+        $childCachedPartialMount = $this->getCachedMountInfo("{$this->mountPoint}partial/", 43);
382
+        $childCachedFullMount = $this->getCachedMountInfo("{$this->mountPoint}full/", 44, SetupManagerTestFullMountProvider::class);
383
+
384
+        $this->userMountCache->expects($this->exactly(2))
385
+            ->method('getMountForPath')
386
+            ->with($this->user, $this->path)
387
+            ->willReturn($parentPartialCachedMount);
388
+        $this->userMountCache->expects($this->exactly(2))
389
+            ->method('getMountsInPath')
390
+            ->with($this->user, $this->path)
391
+            ->willReturn([$childCachedPartialMount, $childCachedFullMount]);
392
+
393
+        $homeMount = $this->createMock(IMountPoint::class);
394
+        $parentPartialMount = $this->createMock(IMountPoint::class);
395
+        $childPartialMount = $this->createMock(IMountPoint::class);
396
+        $childFullProviderMount = $this->createMock(IMountPoint::class);
397
+
398
+        $this->mountProviderCollection->expects($this->once())
399
+            ->method('getHomeMountForUser')
400
+            ->willReturn($homeMount);
401
+
402
+        $this->userMountCache->expects($this->once())
403
+            ->method('registerMounts')
404
+            ->with(
405
+                $this->user, [$childFullProviderMount],
406
+                [SetupManagerTestFullMountProvider::class],
407
+            );
408
+
409
+        $this->fileAccess->expects($this->once())
410
+            ->method('getByFileId')
411
+            ->with(42)
412
+            ->willReturn($this->createMock(CacheEntry::class));
413
+        $childMetadata = $this->createMock(CacheEntry::class);
414
+        $this->fileAccess->expects($this->once())
415
+            ->method('getByFileIds')
416
+            ->with([43])
417
+            ->willReturn([43 => $childMetadata]);
418
+
419
+        $invokedCount = $this->exactly(2);
420
+        $this->mountProviderCollection->expects($invokedCount)
421
+            ->method('getUserMountsFromProviderByPath')
422
+            ->willReturnCallback(function (string $providerClass, string $pathArg, array $mountProviderArgs) use (
423
+                $childCachedPartialMount,
424
+                $childPartialMount,
425
+                $parentPartialMount,
426
+                $parentPartialCachedMount,
427
+                $invokedCount) {
428
+                $expectedPath = $this->path;
429
+                if ($invokedCount->numberOfInvocations() === 1) {
430
+                    // call for the parent
431
+                    $expectedCachedMount = $parentPartialCachedMount;
432
+                    $mountPoints = [$parentPartialMount];
433
+                } else {
434
+                    // call for the children
435
+                    $expectedCachedMount = $childCachedPartialMount;
436
+                    $mountPoints = [$childPartialMount];
437
+                }
438
+
439
+                $this->assertSame(SetupManagerTestPartialMountProvider::class, $providerClass);
440
+                $this->assertSame($expectedPath, $pathArg);
441
+                $this->assertCount(1, $mountProviderArgs);
442
+                $this->assertInstanceOf(IMountProviderArgs::class, $mountProviderArgs[0]);
443
+                $this->assertSame($expectedCachedMount, $mountProviderArgs[0]->mountInfo);
444
+
445
+                return $mountPoints;
446
+            });
447
+
448
+        $this->mountProviderCollection->expects($this->once())
449
+            ->method('getUserMountsForProviderClasses')
450
+            ->with($this->user, [SetupManagerTestFullMountProvider::class])
451
+            ->willReturn([$childFullProviderMount]);
452
+
453
+        $invokedCount = $this->exactly(4);
454
+        $addMountExpectations = [
455
+            1 => $homeMount,
456
+            2 => $childFullProviderMount,
457
+            3 => $parentPartialMount,
458
+            4 => $childPartialMount,
459
+        ];
460
+        $this->mountManager->expects($invokedCount)
461
+            ->method('addMount')
462
+            ->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
463
+
464
+        // call twice to test that providers and mounts are only called once
465
+        $this->setupManager->setupForPath($this->path, true);
466
+        $this->setupManager->setupForPath($this->path, true);
467
+    }
468
+
469
+    private function getAddMountCheckCallback(InvokedCount $invokedCount, $expectations): \Closure {
470
+        return function (IMountPoint $actualMount) use ($invokedCount, $expectations) {
471
+            $expectedMount = $expectations[$invokedCount->numberOfInvocations()] ?? null;
472
+            $this->assertSame($expectedMount, $actualMount);
473
+        };
474
+    }
475
+
476
+    public function getCachedMountInfo(string $mountPoint, int $rootId, string $providerClass = SetupManagerTestPartialMountProvider::class): ICachedMountInfo&MockObject {
477
+        $cachedMount = $this->createMock(ICachedMountInfo::class);
478
+        $cachedMount->method('getMountProvider')->willReturn($providerClass);
479
+        $cachedMount->method('getMountPoint')->willReturn($mountPoint);
480
+        $cachedMount->method('getRootId')->willReturn($rootId);
481
+
482
+        return $cachedMount;
483
+    }
484 484
 }
485 485
 
486 486
 class SetupManagerTestPartialMountProvider implements IPartialMountProvider {
487
-	public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
488
-		return [];
489
-	}
487
+    public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
488
+        return [];
489
+    }
490 490
 
491
-	public function getMountsForPath(string $path, array $mountProviderArgs, IStorageFactory $loader): array {
492
-		return [];
493
-	}
491
+    public function getMountsForPath(string $path, array $mountProviderArgs, IStorageFactory $loader): array {
492
+        return [];
493
+    }
494 494
 }
495 495
 
496 496
 class SetupManagerTestFullMountProvider implements IMountProvider {
497
-	public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
498
-		return [];
499
-	}
497
+    public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
498
+        return [];
499
+    }
500 500
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -140,7 +140,7 @@  discard block
 block discarded – undo
140 140
 			->with(
141 141
 				SetupManagerTestPartialMountProvider::class,
142 142
 				$this->path,
143
-				$this->callback(function (array $args) use ($cachedMount) {
143
+				$this->callback(function(array $args) use ($cachedMount) {
144 144
 					$this->assertCount(1, $args);
145 145
 					$this->assertInstanceOf(IMountProviderArgs::class, $args[0]);
146 146
 					$this->assertSame($cachedMount, $args[0]->mountInfo);
@@ -213,7 +213,7 @@  discard block
 block discarded – undo
213 213
 
214 214
 	public function testSetupForPathWithChildrenAndNonPartialProviderSkipsAlreadySetupProvider(): void {
215 215
 		$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42, IMountProvider::class);
216
-		$additionalCachedMount = $this->getCachedMountInfo($this->mountPoint . 'additional/', 43, SetupManagerTestFullMountProvider::class);
216
+		$additionalCachedMount = $this->getCachedMountInfo($this->mountPoint.'additional/', 43, SetupManagerTestFullMountProvider::class);
217 217
 
218 218
 		$this->userMountCache->expects($this->exactly(2))
219 219
 			->method('getMountForPath')
@@ -230,7 +230,7 @@  discard block
 block discarded – undo
230 230
 		$invokedCount = $this->exactly(2);
231 231
 		$this->mountProviderCollection->expects($invokedCount)
232 232
 			->method('getUserMountsForProviderClasses')
233
-			->willReturnCallback(function (IUser $userArg, array $providersArg) use (
233
+			->willReturnCallback(function(IUser $userArg, array $providersArg) use (
234 234
 				$additionalMount,
235 235
 				$mount,
236 236
 				$invokedCount) {
@@ -279,7 +279,7 @@  discard block
 block discarded – undo
279 279
 		$invokedCount = $this->exactly(3);
280 280
 		$this->userMountCache->expects($invokedCount)
281 281
 			->method('getMountForPath')
282
-			->willReturnCallback(function (IUser $userArg, string $pathArg) use (
282
+			->willReturnCallback(function(IUser $userArg, string $pathArg) use (
283 283
 				$cachedChildMount,
284 284
 				$cachedMount,
285 285
 				$childPath,
@@ -319,7 +319,7 @@  discard block
 block discarded – undo
319 319
 		$invokedCount = $this->exactly(2);
320 320
 		$this->mountProviderCollection->expects($invokedCount)
321 321
 			->method('getUserMountsFromProviderByPath')
322
-			->willReturnCallback(function (
322
+			->willReturnCallback(function(
323 323
 				string $providerClass,
324 324
 				string $pathArg,
325 325
 				array $mountProviderArgs,
@@ -419,7 +419,7 @@  discard block
 block discarded – undo
419 419
 		$invokedCount = $this->exactly(2);
420 420
 		$this->mountProviderCollection->expects($invokedCount)
421 421
 			->method('getUserMountsFromProviderByPath')
422
-			->willReturnCallback(function (string $providerClass, string $pathArg, array $mountProviderArgs) use (
422
+			->willReturnCallback(function(string $providerClass, string $pathArg, array $mountProviderArgs) use (
423 423
 				$childCachedPartialMount,
424 424
 				$childPartialMount,
425 425
 				$parentPartialMount,
@@ -467,7 +467,7 @@  discard block
 block discarded – undo
467 467
 	}
468 468
 
469 469
 	private function getAddMountCheckCallback(InvokedCount $invokedCount, $expectations): \Closure {
470
-		return function (IMountPoint $actualMount) use ($invokedCount, $expectations) {
470
+		return function(IMountPoint $actualMount) use ($invokedCount, $expectations) {
471 471
 			$expectedMount = $expectations[$invokedCount->numberOfInvocations()] ?? null;
472 472
 			$this->assertSame($expectedMount, $actualMount);
473 473
 		};
Please login to merge, or discard this patch.