Completed
Push — master ( c614a1...ed6d0e )
by
unknown
29:26 queued 10s
created
lib/public/Files/Config/IPartialMountProvider.php 1 patch
Indentation   +23 added lines, -23 removed lines patch added patch discarded remove patch
@@ -19,27 +19,27 @@
 block discarded – undo
19 19
  */
20 20
 interface IPartialMountProvider extends IMountProvider {
21 21
 
22
-	/**
23
-	 * Called during the Filesystem setup of a specific path.
24
-	 *
25
-	 * The provided arguments give information about the path being set up,
26
-	 * as well as information about mount points known to be provided by the
27
-	 * mount provider and contained in the path or in its sub-paths.
28
-	 *
29
-	 * Implementations should verify the IMountProviderArgs and return the
30
-	 * corresponding IMountPoint instances.
31
-	 *
32
-	 * @param string $path path for which the mounts are set up
33
-	 * @param bool $forChildren when true, only child mounts for path should be returned
34
-	 * @param IMountProviderArgs[] $mountProviderArgs
35
-	 * @param IStorageFactory $loader
36
-	 * @return array<string, IMountPoint> IMountPoint instances, indexed by
37
-	 *                                    mount-point
38
-	 */
39
-	public function getMountsForPath(
40
-		string $path,
41
-		bool $forChildren,
42
-		array $mountProviderArgs,
43
-		IStorageFactory $loader,
44
-	): array;
22
+    /**
23
+     * Called during the Filesystem setup of a specific path.
24
+     *
25
+     * The provided arguments give information about the path being set up,
26
+     * as well as information about mount points known to be provided by the
27
+     * mount provider and contained in the path or in its sub-paths.
28
+     *
29
+     * Implementations should verify the IMountProviderArgs and return the
30
+     * corresponding IMountPoint instances.
31
+     *
32
+     * @param string $path path for which the mounts are set up
33
+     * @param bool $forChildren when true, only child mounts for path should be returned
34
+     * @param IMountProviderArgs[] $mountProviderArgs
35
+     * @param IStorageFactory $loader
36
+     * @return array<string, IMountPoint> IMountPoint instances, indexed by
37
+     *                                    mount-point
38
+     */
39
+    public function getMountsForPath(
40
+        string $path,
41
+        bool $forChildren,
42
+        array $mountProviderArgs,
43
+        IStorageFactory $loader,
44
+    ): array;
45 45
 }
Please login to merge, or discard this patch.
lib/private/Files/Config/MountProviderCollection.php 1 patch
Indentation   +263 added lines, -263 removed lines patch added patch discarded remove patch
@@ -26,267 +26,267 @@
 block discarded – undo
26 26
 use function in_array;
27 27
 
28 28
 class MountProviderCollection implements IMountProviderCollection, Emitter {
29
-	use EmitterTrait;
30
-
31
-	/**
32
-	 * @var list<IHomeMountProvider>
33
-	 */
34
-	private array $homeProviders = [];
35
-
36
-	/**
37
-	 * @var array<class-string<IMountProvider>, IMountProvider>
38
-	 */
39
-	private array $providers = [];
40
-
41
-	/** @var list<IRootMountProvider> */
42
-	private array $rootProviders = [];
43
-
44
-	/** @var list<callable> */
45
-	private array $mountFilters = [];
46
-
47
-	public function __construct(
48
-		private IStorageFactory $loader,
49
-		private IUserMountCache $mountCache,
50
-		private IEventLogger $eventLogger,
51
-	) {
52
-	}
53
-
54
-	/**
55
-	 * @return list<IMountPoint>
56
-	 */
57
-	private function getMountsFromProvider(IMountProvider $provider, IUser $user, IStorageFactory $loader): array {
58
-		$class = str_replace('\\', '_', get_class($provider));
59
-		$uid = $user->getUID();
60
-		$this->eventLogger->start('fs:setup:provider:' . $class, "Getting mounts from $class for $uid");
61
-		$mounts = $provider->getMountsForUser($user, $loader) ?? [];
62
-		$this->eventLogger->end('fs:setup:provider:' . $class);
63
-		return array_values($mounts);
64
-	}
65
-
66
-	/**
67
-	 * @param list<IMountProvider> $providers
68
-	 * @return list<IMountPoint>
69
-	 */
70
-	private function getUserMountsForProviders(IUser $user, array $providers): array {
71
-		$loader = $this->loader;
72
-		$mounts = array_map(function (IMountProvider $provider) use ($user, $loader) {
73
-			return $this->getMountsFromProvider($provider, $user, $loader);
74
-		}, $providers);
75
-		$mounts = array_merge(...$mounts);
76
-		return $this->filterMounts($user, $mounts);
77
-	}
78
-
79
-	/**
80
-	 * @return list<IMountPoint>
81
-	 */
82
-	public function getMountsForUser(IUser $user): array {
83
-		return $this->getUserMountsForProviders($user, array_values($this->providers));
84
-	}
85
-
86
-	/**
87
-	 * @param IMountProviderArgs[] $mountProviderArgs
88
-	 * @return array<string, IMountPoint> IMountPoint array indexed by mount
89
-	 *                                    point.
90
-	 */
91
-	public function getUserMountsFromProviderByPath(
92
-		string $providerClass,
93
-		string $path,
94
-		bool $forChildren,
95
-		array $mountProviderArgs,
96
-	): array {
97
-		$provider = $this->providers[$providerClass] ?? null;
98
-		if ($provider === null) {
99
-			return [];
100
-		}
101
-
102
-		if (!is_a($providerClass, IPartialMountProvider::class, true)) {
103
-			throw new \LogicException(
104
-				'Mount provider does not support partial mounts'
105
-			);
106
-		}
107
-
108
-		/** @var IPartialMountProvider $provider */
109
-		return $provider->getMountsForPath(
110
-			$path,
111
-			$forChildren,
112
-			$mountProviderArgs,
113
-			$this->loader,
114
-		);
115
-	}
116
-
117
-	/**
118
-	 * Returns the mounts for the user from the specified provider classes.
119
-	 * Providers not registered in the MountProviderCollection will be skipped.
120
-	 *
121
-	 * @inheritdoc
122
-	 *
123
-	 * @return list<IMountPoint>
124
-	 */
125
-	public function getUserMountsForProviderClasses(IUser $user, array $mountProviderClasses): array {
126
-		$providers = array_filter(
127
-			$this->providers,
128
-			fn (string $providerClass) => in_array($providerClass, $mountProviderClasses),
129
-			ARRAY_FILTER_USE_KEY
130
-		);
131
-		return $this->getUserMountsForProviders($user, array_values($providers));
132
-	}
133
-
134
-	/**
135
-	 * @return list<IMountPoint>
136
-	 */
137
-	public function addMountForUser(IUser $user, IMountManager $mountManager, ?callable $providerFilter = null): array {
138
-		// shared mount provider gets to go last since it needs to know existing files
139
-		// to check for name collisions
140
-		$firstMounts = [];
141
-		if ($providerFilter) {
142
-			$providers = array_filter($this->providers, $providerFilter, ARRAY_FILTER_USE_KEY);
143
-		} else {
144
-			$providers = $this->providers;
145
-		}
146
-		$firstProviders
147
-			= array_filter(
148
-				$providers,
149
-				fn (string $providerClass) => ($providerClass !== MountProvider::class),
150
-				ARRAY_FILTER_USE_KEY
151
-			);
152
-		$lastProviders = array_filter(
153
-			$providers,
154
-			fn (string $providerClass) => $providerClass === MountProvider::class,
155
-			ARRAY_FILTER_USE_KEY
156
-		);
157
-		foreach ($firstProviders as $provider) {
158
-			$mounts = $this->getMountsFromProvider($provider, $user, $this->loader);
159
-			$firstMounts = array_merge($firstMounts, $mounts);
160
-		}
161
-		$firstMounts = $this->filterMounts($user, $firstMounts);
162
-		array_walk($firstMounts, [$mountManager, 'addMount']);
163
-
164
-		$lateMounts = [];
165
-		foreach ($lastProviders as $provider) {
166
-			$mounts = $this->getMountsFromProvider($provider, $user, $this->loader);
167
-			$lateMounts = array_merge($lateMounts, $mounts);
168
-		}
169
-
170
-		$lateMounts = $this->filterMounts($user, $lateMounts);
171
-		$this->eventLogger->start('fs:setup:add-mounts', 'Add mounts to the filesystem');
172
-		array_walk($lateMounts, [$mountManager, 'addMount']);
173
-		$this->eventLogger->end('fs:setup:add-mounts');
174
-
175
-		return array_values(array_merge($lateMounts, $firstMounts));
176
-	}
177
-
178
-	/**
179
-	 * Get the configured home mount for this user
180
-	 *
181
-	 * @since 9.1.0
182
-	 */
183
-	public function getHomeMountForUser(IUser $user): IMountPoint {
184
-		$providers = array_reverse($this->homeProviders); // call the latest registered provider first to give apps an opportunity to overwrite builtin
185
-		foreach ($providers as $homeProvider) {
186
-			if ($mount = $homeProvider->getHomeMountForUser($user, $this->loader)) {
187
-				$mount->setMountPoint('/' . $user->getUID()); //make sure the mountpoint is what we expect
188
-				return $mount;
189
-			}
190
-		}
191
-		throw new \Exception('No home storage configured for user ' . $user);
192
-	}
193
-
194
-	/**
195
-	 * Add a provider for mount points
196
-	 */
197
-	public function registerProvider(IMountProvider $provider): void {
198
-		$this->providers[get_class($provider)] = $provider;
199
-
200
-		$this->emit('\OC\Files\Config', 'registerMountProvider', [$provider]);
201
-	}
202
-
203
-	public function registerMountFilter(callable $filter): void {
204
-		$this->mountFilters[] = $filter;
205
-	}
206
-
207
-	/**
208
-	 * @param list<IMountPoint> $mountPoints
209
-	 * @return list<IMountPoint>
210
-	 */
211
-	private function filterMounts(IUser $user, array $mountPoints): array {
212
-		return array_values(array_filter($mountPoints, function (IMountPoint $mountPoint) use ($user) {
213
-			foreach ($this->mountFilters as $filter) {
214
-				if ($filter($mountPoint, $user) === false) {
215
-					return false;
216
-				}
217
-			}
218
-			return true;
219
-		}));
220
-	}
221
-
222
-	/**
223
-	 * Add a provider for home mount points
224
-	 *
225
-	 * @param IHomeMountProvider $provider
226
-	 * @since 9.1.0
227
-	 */
228
-	public function registerHomeProvider(IHomeMountProvider $provider) {
229
-		$this->homeProviders[] = $provider;
230
-		$this->emit('\OC\Files\Config', 'registerHomeMountProvider', [$provider]);
231
-	}
232
-
233
-	/**
234
-	 * Get the mount cache which can be used to search for mounts without setting up the filesystem
235
-	 */
236
-	public function getMountCache(): IUserMountCache {
237
-		return $this->mountCache;
238
-	}
239
-
240
-	public function registerRootProvider(IRootMountProvider $provider): void {
241
-		$this->rootProviders[] = $provider;
242
-	}
243
-
244
-	/**
245
-	 * Get all root mountpoints
246
-	 *
247
-	 * @return list<IMountPoint>
248
-	 * @since 20.0.0
249
-	 */
250
-	public function getRootMounts(): array {
251
-		$loader = $this->loader;
252
-		$mounts = array_map(function (IRootMountProvider $provider) use ($loader) {
253
-			return $provider->getRootMounts($loader);
254
-		}, $this->rootProviders);
255
-		$mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) {
256
-			return array_merge($mounts, $providerMounts);
257
-		}, []);
258
-
259
-		if (count($mounts) === 0) {
260
-			throw new \Exception('No root mounts provided by any provider');
261
-		}
262
-
263
-		return array_values($mounts);
264
-	}
265
-
266
-	public function clearProviders(): void {
267
-		$this->providers = [];
268
-		$this->homeProviders = [];
269
-		$this->rootProviders = [];
270
-	}
271
-
272
-	/**
273
-	 * @return list<IMountProvider>
274
-	 */
275
-	public function getProviders(): array {
276
-		return array_values($this->providers);
277
-	}
278
-
279
-	/**
280
-	 * @return list<IHomeMountProvider>
281
-	 */
282
-	public function getHomeProviders(): array {
283
-		return $this->homeProviders;
284
-	}
285
-
286
-	/**
287
-	 * @return list<IRootMountProvider>
288
-	 */
289
-	public function getRootProviders(): array {
290
-		return $this->rootProviders;
291
-	}
29
+    use EmitterTrait;
30
+
31
+    /**
32
+     * @var list<IHomeMountProvider>
33
+     */
34
+    private array $homeProviders = [];
35
+
36
+    /**
37
+     * @var array<class-string<IMountProvider>, IMountProvider>
38
+     */
39
+    private array $providers = [];
40
+
41
+    /** @var list<IRootMountProvider> */
42
+    private array $rootProviders = [];
43
+
44
+    /** @var list<callable> */
45
+    private array $mountFilters = [];
46
+
47
+    public function __construct(
48
+        private IStorageFactory $loader,
49
+        private IUserMountCache $mountCache,
50
+        private IEventLogger $eventLogger,
51
+    ) {
52
+    }
53
+
54
+    /**
55
+     * @return list<IMountPoint>
56
+     */
57
+    private function getMountsFromProvider(IMountProvider $provider, IUser $user, IStorageFactory $loader): array {
58
+        $class = str_replace('\\', '_', get_class($provider));
59
+        $uid = $user->getUID();
60
+        $this->eventLogger->start('fs:setup:provider:' . $class, "Getting mounts from $class for $uid");
61
+        $mounts = $provider->getMountsForUser($user, $loader) ?? [];
62
+        $this->eventLogger->end('fs:setup:provider:' . $class);
63
+        return array_values($mounts);
64
+    }
65
+
66
+    /**
67
+     * @param list<IMountProvider> $providers
68
+     * @return list<IMountPoint>
69
+     */
70
+    private function getUserMountsForProviders(IUser $user, array $providers): array {
71
+        $loader = $this->loader;
72
+        $mounts = array_map(function (IMountProvider $provider) use ($user, $loader) {
73
+            return $this->getMountsFromProvider($provider, $user, $loader);
74
+        }, $providers);
75
+        $mounts = array_merge(...$mounts);
76
+        return $this->filterMounts($user, $mounts);
77
+    }
78
+
79
+    /**
80
+     * @return list<IMountPoint>
81
+     */
82
+    public function getMountsForUser(IUser $user): array {
83
+        return $this->getUserMountsForProviders($user, array_values($this->providers));
84
+    }
85
+
86
+    /**
87
+     * @param IMountProviderArgs[] $mountProviderArgs
88
+     * @return array<string, IMountPoint> IMountPoint array indexed by mount
89
+     *                                    point.
90
+     */
91
+    public function getUserMountsFromProviderByPath(
92
+        string $providerClass,
93
+        string $path,
94
+        bool $forChildren,
95
+        array $mountProviderArgs,
96
+    ): array {
97
+        $provider = $this->providers[$providerClass] ?? null;
98
+        if ($provider === null) {
99
+            return [];
100
+        }
101
+
102
+        if (!is_a($providerClass, IPartialMountProvider::class, true)) {
103
+            throw new \LogicException(
104
+                'Mount provider does not support partial mounts'
105
+            );
106
+        }
107
+
108
+        /** @var IPartialMountProvider $provider */
109
+        return $provider->getMountsForPath(
110
+            $path,
111
+            $forChildren,
112
+            $mountProviderArgs,
113
+            $this->loader,
114
+        );
115
+    }
116
+
117
+    /**
118
+     * Returns the mounts for the user from the specified provider classes.
119
+     * Providers not registered in the MountProviderCollection will be skipped.
120
+     *
121
+     * @inheritdoc
122
+     *
123
+     * @return list<IMountPoint>
124
+     */
125
+    public function getUserMountsForProviderClasses(IUser $user, array $mountProviderClasses): array {
126
+        $providers = array_filter(
127
+            $this->providers,
128
+            fn (string $providerClass) => in_array($providerClass, $mountProviderClasses),
129
+            ARRAY_FILTER_USE_KEY
130
+        );
131
+        return $this->getUserMountsForProviders($user, array_values($providers));
132
+    }
133
+
134
+    /**
135
+     * @return list<IMountPoint>
136
+     */
137
+    public function addMountForUser(IUser $user, IMountManager $mountManager, ?callable $providerFilter = null): array {
138
+        // shared mount provider gets to go last since it needs to know existing files
139
+        // to check for name collisions
140
+        $firstMounts = [];
141
+        if ($providerFilter) {
142
+            $providers = array_filter($this->providers, $providerFilter, ARRAY_FILTER_USE_KEY);
143
+        } else {
144
+            $providers = $this->providers;
145
+        }
146
+        $firstProviders
147
+            = array_filter(
148
+                $providers,
149
+                fn (string $providerClass) => ($providerClass !== MountProvider::class),
150
+                ARRAY_FILTER_USE_KEY
151
+            );
152
+        $lastProviders = array_filter(
153
+            $providers,
154
+            fn (string $providerClass) => $providerClass === MountProvider::class,
155
+            ARRAY_FILTER_USE_KEY
156
+        );
157
+        foreach ($firstProviders as $provider) {
158
+            $mounts = $this->getMountsFromProvider($provider, $user, $this->loader);
159
+            $firstMounts = array_merge($firstMounts, $mounts);
160
+        }
161
+        $firstMounts = $this->filterMounts($user, $firstMounts);
162
+        array_walk($firstMounts, [$mountManager, 'addMount']);
163
+
164
+        $lateMounts = [];
165
+        foreach ($lastProviders as $provider) {
166
+            $mounts = $this->getMountsFromProvider($provider, $user, $this->loader);
167
+            $lateMounts = array_merge($lateMounts, $mounts);
168
+        }
169
+
170
+        $lateMounts = $this->filterMounts($user, $lateMounts);
171
+        $this->eventLogger->start('fs:setup:add-mounts', 'Add mounts to the filesystem');
172
+        array_walk($lateMounts, [$mountManager, 'addMount']);
173
+        $this->eventLogger->end('fs:setup:add-mounts');
174
+
175
+        return array_values(array_merge($lateMounts, $firstMounts));
176
+    }
177
+
178
+    /**
179
+     * Get the configured home mount for this user
180
+     *
181
+     * @since 9.1.0
182
+     */
183
+    public function getHomeMountForUser(IUser $user): IMountPoint {
184
+        $providers = array_reverse($this->homeProviders); // call the latest registered provider first to give apps an opportunity to overwrite builtin
185
+        foreach ($providers as $homeProvider) {
186
+            if ($mount = $homeProvider->getHomeMountForUser($user, $this->loader)) {
187
+                $mount->setMountPoint('/' . $user->getUID()); //make sure the mountpoint is what we expect
188
+                return $mount;
189
+            }
190
+        }
191
+        throw new \Exception('No home storage configured for user ' . $user);
192
+    }
193
+
194
+    /**
195
+     * Add a provider for mount points
196
+     */
197
+    public function registerProvider(IMountProvider $provider): void {
198
+        $this->providers[get_class($provider)] = $provider;
199
+
200
+        $this->emit('\OC\Files\Config', 'registerMountProvider', [$provider]);
201
+    }
202
+
203
+    public function registerMountFilter(callable $filter): void {
204
+        $this->mountFilters[] = $filter;
205
+    }
206
+
207
+    /**
208
+     * @param list<IMountPoint> $mountPoints
209
+     * @return list<IMountPoint>
210
+     */
211
+    private function filterMounts(IUser $user, array $mountPoints): array {
212
+        return array_values(array_filter($mountPoints, function (IMountPoint $mountPoint) use ($user) {
213
+            foreach ($this->mountFilters as $filter) {
214
+                if ($filter($mountPoint, $user) === false) {
215
+                    return false;
216
+                }
217
+            }
218
+            return true;
219
+        }));
220
+    }
221
+
222
+    /**
223
+     * Add a provider for home mount points
224
+     *
225
+     * @param IHomeMountProvider $provider
226
+     * @since 9.1.0
227
+     */
228
+    public function registerHomeProvider(IHomeMountProvider $provider) {
229
+        $this->homeProviders[] = $provider;
230
+        $this->emit('\OC\Files\Config', 'registerHomeMountProvider', [$provider]);
231
+    }
232
+
233
+    /**
234
+     * Get the mount cache which can be used to search for mounts without setting up the filesystem
235
+     */
236
+    public function getMountCache(): IUserMountCache {
237
+        return $this->mountCache;
238
+    }
239
+
240
+    public function registerRootProvider(IRootMountProvider $provider): void {
241
+        $this->rootProviders[] = $provider;
242
+    }
243
+
244
+    /**
245
+     * Get all root mountpoints
246
+     *
247
+     * @return list<IMountPoint>
248
+     * @since 20.0.0
249
+     */
250
+    public function getRootMounts(): array {
251
+        $loader = $this->loader;
252
+        $mounts = array_map(function (IRootMountProvider $provider) use ($loader) {
253
+            return $provider->getRootMounts($loader);
254
+        }, $this->rootProviders);
255
+        $mounts = array_reduce($mounts, function (array $mounts, array $providerMounts) {
256
+            return array_merge($mounts, $providerMounts);
257
+        }, []);
258
+
259
+        if (count($mounts) === 0) {
260
+            throw new \Exception('No root mounts provided by any provider');
261
+        }
262
+
263
+        return array_values($mounts);
264
+    }
265
+
266
+    public function clearProviders(): void {
267
+        $this->providers = [];
268
+        $this->homeProviders = [];
269
+        $this->rootProviders = [];
270
+    }
271
+
272
+    /**
273
+     * @return list<IMountProvider>
274
+     */
275
+    public function getProviders(): array {
276
+        return array_values($this->providers);
277
+    }
278
+
279
+    /**
280
+     * @return list<IHomeMountProvider>
281
+     */
282
+    public function getHomeProviders(): array {
283
+        return $this->homeProviders;
284
+    }
285
+
286
+    /**
287
+     * @return list<IRootMountProvider>
288
+     */
289
+    public function getRootProviders(): array {
290
+        return $this->rootProviders;
291
+    }
292 292
 }
Please login to merge, or discard this patch.
lib/private/Files/SetupManager.php 1 patch
Indentation   +677 added lines, -677 removed lines patch added patch discarded remove patch
@@ -62,683 +62,683 @@
 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
-						false,
490
-						[$providerArgs]
491
-					)
492
-				);
493
-			} else {
494
-				$currentProviders[] = $mountProvider;
495
-				$setupProviders[] = $mountProvider;
496
-				$fullProviderMounts[]
497
-					= $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$mountProvider]);
498
-			}
499
-		}
500
-
501
-		if ($includeChildren) {
502
-			$subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
503
-			$this->eventLogger->end('fs:setup:user:path:find');
504
-
505
-			$needsFullSetup
506
-				= array_any(
507
-					$subCachedMounts,
508
-					fn (ICachedMountInfo $info) => $info->getMountProvider() === ''
509
-				);
510
-
511
-			if ($needsFullSetup) {
512
-				$this->logger->debug('mount has no provider set, performing full setup');
513
-				$this->setupForUser($user);
514
-				$this->eventLogger->end('fs:setup:user:path');
515
-				return;
516
-			}
517
-
518
-			/** @var array<class-string<IMountProvider>, ICachedMountInfo[]> $authoritativeCachedMounts */
519
-			$authoritativeCachedMounts = [];
520
-			foreach ($subCachedMounts as $cachedMount) {
521
-				/** @var class-string<IMountProvider> $mountProvider */
522
-				$mountProvider = $cachedMount->getMountProvider();
523
-
524
-				// skip setup for already set up providers
525
-				if (in_array($mountProvider, $setupProviders)) {
526
-					continue;
527
-				}
528
-
529
-				if (is_a($mountProvider, IPartialMountProvider::class, true)) {
530
-					// skip setup if path was set up as authoritative before
531
-					if ($this->isPathSetup($cachedMount->getMountPoint())) {
532
-						continue;
533
-					}
534
-					// collect cached mount points for authoritative providers
535
-					$authoritativeCachedMounts[$mountProvider] ??= [];
536
-					$authoritativeCachedMounts[$mountProvider][] = $cachedMount;
537
-					continue;
538
-				}
539
-
540
-				$currentProviders[] = $mountProvider;
541
-				$setupProviders[] = $mountProvider;
542
-				$fullProviderMounts[]
543
-					= $this->mountProviderCollection->getUserMountsForProviderClasses(
544
-						$user,
545
-						[$mountProvider]
546
-					);
547
-			}
548
-
549
-			if (!empty($authoritativeCachedMounts)) {
550
-				$rootIds = array_map(
551
-					fn (ICachedMountInfo $mount) => $mount->getRootId(),
552
-					array_merge(...array_values($authoritativeCachedMounts)),
553
-				);
554
-
555
-				$rootsMetadata = [];
556
-				foreach (array_chunk($rootIds, 1000) as $chunk) {
557
-					foreach ($this->fileAccess->getByFileIds($chunk) as $id => $fileMetadata) {
558
-						$rootsMetadata[$id] = $fileMetadata;
559
-					}
560
-				}
561
-				$this->setupMountProviderPaths[$mountPoint] = self::SETUP_WITH_CHILDREN;
562
-				foreach ($authoritativeCachedMounts as $providerClass => $cachedMounts) {
563
-					$providerArgs = array_filter(array_map(
564
-						static function (ICachedMountInfo $info) use ($rootsMetadata) {
565
-							$rootMetadata = $rootsMetadata[$info->getRootId()] ?? null;
566
-
567
-							return $rootMetadata
568
-								? new IMountProviderArgs($info, $rootMetadata)
569
-								: null;
570
-						},
571
-						$cachedMounts
572
-					));
573
-					$authoritativeMounts[]
574
-						= $this->mountProviderCollection->getUserMountsFromProviderByPath(
575
-							$providerClass,
576
-							$path,
577
-							true,
578
-							$providerArgs,
579
-						);
580
-				}
581
-			}
582
-		} else {
583
-			$this->eventLogger->end('fs:setup:user:path:find');
584
-		}
585
-
586
-		$fullProviderMounts = array_merge(...$fullProviderMounts);
587
-		$authoritativeMounts = array_merge(...$authoritativeMounts);
588
-
589
-		if (count($fullProviderMounts) || count($authoritativeMounts)) {
590
-			if (count($fullProviderMounts)) {
591
-				$this->registerMounts($user, $fullProviderMounts, $currentProviders);
592
-			}
593
-
594
-			$this->setupForUserWith($user, function () use ($fullProviderMounts, $authoritativeMounts) {
595
-				$allMounts = [...$fullProviderMounts, ...$authoritativeMounts];
596
-				array_walk($allMounts, $this->mountManager->addMount(...));
597
-			});
598
-		} elseif (!$this->isSetupStarted($user)) {
599
-			$this->oneTimeUserSetup($user);
600
-		}
601
-		$this->eventLogger->end('fs:setup:user:path');
602
-	}
603
-
604
-	private function fullSetupRequired(IUser $user): bool {
605
-		if ($this->forceFullSetup) {
606
-			return true;
607
-		}
608
-
609
-		// we perform a "cached" setup only after having done the full setup recently
610
-		// this is also used to trigger a full setup after handling events that are likely
611
-		// to change the available mounts
612
-		if (!isset($this->fullSetupRequired[$user->getUID()])) {
613
-			$this->fullSetupRequired[$user->getUID()] = !$this->cache->get($user->getUID());
614
-		}
615
-		return $this->fullSetupRequired[$user->getUID()];
616
-	}
617
-
618
-	/**
619
-	 * @param string $path
620
-	 * @param string[] $providers
621
-	 */
622
-	public function setupForProvider(string $path, array $providers): void {
623
-		$user = $this->getUserForPath($path);
624
-		if (!$user) {
625
-			$this->setupRoot();
626
-			return;
627
-		}
628
-
629
-		if ($this->isSetupComplete($user)) {
630
-			return;
631
-		}
632
-
633
-		if ($this->fullSetupRequired($user)) {
634
-			$this->setupForUser($user);
635
-			return;
636
-		}
637
-
638
-		$this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
639
-
640
-		$this->oneTimeUserSetup($user);
641
-
642
-		// home providers are always used
643
-		$providers = array_filter($providers, function (string $provider) {
644
-			return !is_subclass_of($provider, IHomeMountProvider::class);
645
-		});
646
-
647
-		if (in_array('', $providers)) {
648
-			$this->setupForUser($user);
649
-			return;
650
-		}
651
-		$setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
652
-
653
-		$providers = array_diff($providers, $setupProviders);
654
-		if (count($providers) === 0) {
655
-			if (!$this->isSetupStarted($user)) {
656
-				$this->oneTimeUserSetup($user);
657
-			}
658
-			$this->eventLogger->end('fs:setup:user:providers');
659
-			return;
660
-		} else {
661
-			$this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
662
-			$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
663
-		}
664
-
665
-		$this->registerMounts($user, $mounts, $providers);
666
-		$this->setupForUserWith($user, function () use ($mounts) {
667
-			array_walk($mounts, [$this->mountManager, 'addMount']);
668
-		});
669
-		$this->eventLogger->end('fs:setup:user:providers');
670
-	}
671
-
672
-	public function tearDown() {
673
-		$this->setupUsers = [];
674
-		$this->setupUsersComplete = [];
675
-		$this->setupUserMountProviders = [];
676
-		$this->setupMountProviderPaths = [];
677
-		$this->fullSetupRequired = [];
678
-		$this->rootSetup = false;
679
-		$this->mountManager->clear();
680
-		$this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
681
-	}
682
-
683
-	/**
684
-	 * Get mounts from mount providers that are registered after setup
685
-	 */
686
-	private function listenForNewMountProviders() {
687
-		if (!$this->listeningForProviders) {
688
-			$this->listeningForProviders = true;
689
-			$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
690
-				IMountProvider $provider,
691
-			) {
692
-				foreach ($this->setupUsers as $userId) {
693
-					$user = $this->userManager->get($userId);
694
-					if ($user) {
695
-						$mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
696
-						array_walk($mounts, [$this->mountManager, 'addMount']);
697
-					}
698
-				}
699
-			});
700
-		}
701
-	}
702
-
703
-	private function setupListeners() {
704
-		// note that this event handling is intentionally pessimistic
705
-		// clearing the cache to often is better than not enough
706
-
707
-		$this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
708
-			$this->cache->remove($event->getUser()->getUID());
709
-		});
710
-		$this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
711
-			$this->cache->remove($event->getUser()->getUID());
712
-		});
713
-		$this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
714
-			$this->cache->remove($event->getShare()->getSharedWith());
715
-		});
716
-		$this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
717
-		) {
718
-			if ($user = $event->getUser()) {
719
-				$this->cache->remove($user->getUID());
720
-			} else {
721
-				$this->cache->clear();
722
-			}
723
-		});
724
-
725
-		$genericEvents = [
726
-			'OCA\Circles\Events\CreatingCircleEvent',
727
-			'OCA\Circles\Events\DestroyingCircleEvent',
728
-			'OCA\Circles\Events\AddingCircleMemberEvent',
729
-			'OCA\Circles\Events\RemovingCircleMemberEvent',
730
-		];
731
-
732
-		foreach ($genericEvents as $genericEvent) {
733
-			$this->eventDispatcher->addListener($genericEvent, function ($event) {
734
-				$this->cache->clear();
735
-			});
736
-		}
737
-	}
738
-
739
-	private function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null): void {
740
-		if ($this->lockdownManager->canAccessFilesystem()) {
741
-			$this->userMountCache->registerMounts($user, $mounts, $mountProviderClasses);
742
-		}
743
-	}
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
+                        false,
490
+                        [$providerArgs]
491
+                    )
492
+                );
493
+            } else {
494
+                $currentProviders[] = $mountProvider;
495
+                $setupProviders[] = $mountProvider;
496
+                $fullProviderMounts[]
497
+                    = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$mountProvider]);
498
+            }
499
+        }
500
+
501
+        if ($includeChildren) {
502
+            $subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
503
+            $this->eventLogger->end('fs:setup:user:path:find');
504
+
505
+            $needsFullSetup
506
+                = array_any(
507
+                    $subCachedMounts,
508
+                    fn (ICachedMountInfo $info) => $info->getMountProvider() === ''
509
+                );
510
+
511
+            if ($needsFullSetup) {
512
+                $this->logger->debug('mount has no provider set, performing full setup');
513
+                $this->setupForUser($user);
514
+                $this->eventLogger->end('fs:setup:user:path');
515
+                return;
516
+            }
517
+
518
+            /** @var array<class-string<IMountProvider>, ICachedMountInfo[]> $authoritativeCachedMounts */
519
+            $authoritativeCachedMounts = [];
520
+            foreach ($subCachedMounts as $cachedMount) {
521
+                /** @var class-string<IMountProvider> $mountProvider */
522
+                $mountProvider = $cachedMount->getMountProvider();
523
+
524
+                // skip setup for already set up providers
525
+                if (in_array($mountProvider, $setupProviders)) {
526
+                    continue;
527
+                }
528
+
529
+                if (is_a($mountProvider, IPartialMountProvider::class, true)) {
530
+                    // skip setup if path was set up as authoritative before
531
+                    if ($this->isPathSetup($cachedMount->getMountPoint())) {
532
+                        continue;
533
+                    }
534
+                    // collect cached mount points for authoritative providers
535
+                    $authoritativeCachedMounts[$mountProvider] ??= [];
536
+                    $authoritativeCachedMounts[$mountProvider][] = $cachedMount;
537
+                    continue;
538
+                }
539
+
540
+                $currentProviders[] = $mountProvider;
541
+                $setupProviders[] = $mountProvider;
542
+                $fullProviderMounts[]
543
+                    = $this->mountProviderCollection->getUserMountsForProviderClasses(
544
+                        $user,
545
+                        [$mountProvider]
546
+                    );
547
+            }
548
+
549
+            if (!empty($authoritativeCachedMounts)) {
550
+                $rootIds = array_map(
551
+                    fn (ICachedMountInfo $mount) => $mount->getRootId(),
552
+                    array_merge(...array_values($authoritativeCachedMounts)),
553
+                );
554
+
555
+                $rootsMetadata = [];
556
+                foreach (array_chunk($rootIds, 1000) as $chunk) {
557
+                    foreach ($this->fileAccess->getByFileIds($chunk) as $id => $fileMetadata) {
558
+                        $rootsMetadata[$id] = $fileMetadata;
559
+                    }
560
+                }
561
+                $this->setupMountProviderPaths[$mountPoint] = self::SETUP_WITH_CHILDREN;
562
+                foreach ($authoritativeCachedMounts as $providerClass => $cachedMounts) {
563
+                    $providerArgs = array_filter(array_map(
564
+                        static function (ICachedMountInfo $info) use ($rootsMetadata) {
565
+                            $rootMetadata = $rootsMetadata[$info->getRootId()] ?? null;
566
+
567
+                            return $rootMetadata
568
+                                ? new IMountProviderArgs($info, $rootMetadata)
569
+                                : null;
570
+                        },
571
+                        $cachedMounts
572
+                    ));
573
+                    $authoritativeMounts[]
574
+                        = $this->mountProviderCollection->getUserMountsFromProviderByPath(
575
+                            $providerClass,
576
+                            $path,
577
+                            true,
578
+                            $providerArgs,
579
+                        );
580
+                }
581
+            }
582
+        } else {
583
+            $this->eventLogger->end('fs:setup:user:path:find');
584
+        }
585
+
586
+        $fullProviderMounts = array_merge(...$fullProviderMounts);
587
+        $authoritativeMounts = array_merge(...$authoritativeMounts);
588
+
589
+        if (count($fullProviderMounts) || count($authoritativeMounts)) {
590
+            if (count($fullProviderMounts)) {
591
+                $this->registerMounts($user, $fullProviderMounts, $currentProviders);
592
+            }
593
+
594
+            $this->setupForUserWith($user, function () use ($fullProviderMounts, $authoritativeMounts) {
595
+                $allMounts = [...$fullProviderMounts, ...$authoritativeMounts];
596
+                array_walk($allMounts, $this->mountManager->addMount(...));
597
+            });
598
+        } elseif (!$this->isSetupStarted($user)) {
599
+            $this->oneTimeUserSetup($user);
600
+        }
601
+        $this->eventLogger->end('fs:setup:user:path');
602
+    }
603
+
604
+    private function fullSetupRequired(IUser $user): bool {
605
+        if ($this->forceFullSetup) {
606
+            return true;
607
+        }
608
+
609
+        // we perform a "cached" setup only after having done the full setup recently
610
+        // this is also used to trigger a full setup after handling events that are likely
611
+        // to change the available mounts
612
+        if (!isset($this->fullSetupRequired[$user->getUID()])) {
613
+            $this->fullSetupRequired[$user->getUID()] = !$this->cache->get($user->getUID());
614
+        }
615
+        return $this->fullSetupRequired[$user->getUID()];
616
+    }
617
+
618
+    /**
619
+     * @param string $path
620
+     * @param string[] $providers
621
+     */
622
+    public function setupForProvider(string $path, array $providers): void {
623
+        $user = $this->getUserForPath($path);
624
+        if (!$user) {
625
+            $this->setupRoot();
626
+            return;
627
+        }
628
+
629
+        if ($this->isSetupComplete($user)) {
630
+            return;
631
+        }
632
+
633
+        if ($this->fullSetupRequired($user)) {
634
+            $this->setupForUser($user);
635
+            return;
636
+        }
637
+
638
+        $this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
639
+
640
+        $this->oneTimeUserSetup($user);
641
+
642
+        // home providers are always used
643
+        $providers = array_filter($providers, function (string $provider) {
644
+            return !is_subclass_of($provider, IHomeMountProvider::class);
645
+        });
646
+
647
+        if (in_array('', $providers)) {
648
+            $this->setupForUser($user);
649
+            return;
650
+        }
651
+        $setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
652
+
653
+        $providers = array_diff($providers, $setupProviders);
654
+        if (count($providers) === 0) {
655
+            if (!$this->isSetupStarted($user)) {
656
+                $this->oneTimeUserSetup($user);
657
+            }
658
+            $this->eventLogger->end('fs:setup:user:providers');
659
+            return;
660
+        } else {
661
+            $this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
662
+            $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
663
+        }
664
+
665
+        $this->registerMounts($user, $mounts, $providers);
666
+        $this->setupForUserWith($user, function () use ($mounts) {
667
+            array_walk($mounts, [$this->mountManager, 'addMount']);
668
+        });
669
+        $this->eventLogger->end('fs:setup:user:providers');
670
+    }
671
+
672
+    public function tearDown() {
673
+        $this->setupUsers = [];
674
+        $this->setupUsersComplete = [];
675
+        $this->setupUserMountProviders = [];
676
+        $this->setupMountProviderPaths = [];
677
+        $this->fullSetupRequired = [];
678
+        $this->rootSetup = false;
679
+        $this->mountManager->clear();
680
+        $this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
681
+    }
682
+
683
+    /**
684
+     * Get mounts from mount providers that are registered after setup
685
+     */
686
+    private function listenForNewMountProviders() {
687
+        if (!$this->listeningForProviders) {
688
+            $this->listeningForProviders = true;
689
+            $this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
690
+                IMountProvider $provider,
691
+            ) {
692
+                foreach ($this->setupUsers as $userId) {
693
+                    $user = $this->userManager->get($userId);
694
+                    if ($user) {
695
+                        $mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
696
+                        array_walk($mounts, [$this->mountManager, 'addMount']);
697
+                    }
698
+                }
699
+            });
700
+        }
701
+    }
702
+
703
+    private function setupListeners() {
704
+        // note that this event handling is intentionally pessimistic
705
+        // clearing the cache to often is better than not enough
706
+
707
+        $this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
708
+            $this->cache->remove($event->getUser()->getUID());
709
+        });
710
+        $this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
711
+            $this->cache->remove($event->getUser()->getUID());
712
+        });
713
+        $this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
714
+            $this->cache->remove($event->getShare()->getSharedWith());
715
+        });
716
+        $this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
717
+        ) {
718
+            if ($user = $event->getUser()) {
719
+                $this->cache->remove($user->getUID());
720
+            } else {
721
+                $this->cache->clear();
722
+            }
723
+        });
724
+
725
+        $genericEvents = [
726
+            'OCA\Circles\Events\CreatingCircleEvent',
727
+            'OCA\Circles\Events\DestroyingCircleEvent',
728
+            'OCA\Circles\Events\AddingCircleMemberEvent',
729
+            'OCA\Circles\Events\RemovingCircleMemberEvent',
730
+        ];
731
+
732
+        foreach ($genericEvents as $genericEvent) {
733
+            $this->eventDispatcher->addListener($genericEvent, function ($event) {
734
+                $this->cache->clear();
735
+            });
736
+        }
737
+    }
738
+
739
+    private function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null): void {
740
+        if ($this->lockdownManager->canAccessFilesystem()) {
741
+            $this->userMountCache->registerMounts($user, $mounts, $mountProviderClasses);
742
+        }
743
+    }
744 744
 }
Please login to merge, or discard this patch.
tests/lib/Files/SetupManagerTest.php 2 patches
Indentation   +482 added lines, -482 removed lines patch added patch discarded remove patch
@@ -37,493 +37,493 @@
 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
-	/**
122
-	 * Tests that a path is not set up twice for providers implementing
123
-	 * IPartialMountProvider in setupForPath.
124
-	 */
125
-	public function testSetupForPathWithPartialProviderSkipsAlreadySetupPath(): void {
126
-		$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
127
-
128
-		$this->userMountCache->expects($this->exactly(2))
129
-			->method('getMountForPath')
130
-			->with($this->user, $this->path)
131
-			->willReturn($cachedMount);
132
-		$this->userMountCache->expects($this->never())->method('registerMounts');
133
-		$this->userMountCache->expects($this->never())->method('getMountsInPath');
134
-
135
-		$this->fileAccess->expects($this->once())
136
-			->method('getByFileId')
137
-			->with(42)
138
-			->willReturn($this->createMock(CacheEntry::class));
139
-
140
-		$partialMount = $this->createMock(IMountPoint::class);
141
-
142
-		$this->mountProviderCollection->expects($this->once())
143
-			->method('getUserMountsFromProviderByPath')
144
-			->with(
145
-				SetupManagerTestPartialMountProvider::class,
146
-				$this->path,
147
-				false,
148
-				$this->callback(function (array $args) use ($cachedMount) {
149
-					$this->assertCount(1, $args);
150
-					$this->assertInstanceOf(IMountProviderArgs::class, $args[0]);
151
-					$this->assertSame($cachedMount, $args[0]->mountInfo);
152
-					return true;
153
-				})
154
-			)
155
-			->willReturn([$partialMount]);
156
-
157
-		$homeMount = $this->createMock(IMountPoint::class);
158
-
159
-		$this->mountProviderCollection->expects($this->once())
160
-			->method('getHomeMountForUser')
161
-			->willReturn($homeMount);
162
-
163
-		$this->mountProviderCollection->expects($this->never())
164
-			->method('getUserMountsForProviderClasses');
165
-
166
-		$invokedCount = $this->exactly(2);
167
-		$addMountExpectations = [
168
-			1 => $homeMount,
169
-			2 => $partialMount,
170
-		];
171
-		$this->mountManager->expects($invokedCount)
172
-			->method('addMount')
173
-			->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
174
-
175
-		// setup called twice, provider should only be called once
176
-		$this->setupManager->setupForPath($this->path, false);
177
-		$this->setupManager->setupForPath($this->path, false);
178
-	}
179
-
180
-	/**
181
-	 * Tests that providers that are not implementing IPartialMountProvider are
182
-	 * not set up more than once by setupForPath.
183
-	 */
184
-	public function testSetupForPathWithNonPartialProviderSkipsAlreadySetupProvider(): void {
185
-		$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42,
186
-			IMountProvider::class);
187
-
188
-		$this->userMountCache->expects($this->exactly(2))
189
-			->method('getMountForPath')
190
-			->with($this->user, $this->path)
191
-			->willReturn($cachedMount);
192
-
193
-		$this->userMountCache->expects($this->once())->method('registerMounts');
194
-		$this->userMountCache->expects($this->never())->method('getMountsInPath');
195
-
196
-		$providerMount = $this->createMock(IMountPoint::class);
197
-
198
-		$this->mountProviderCollection->expects($this->once())
199
-			->method('getUserMountsForProviderClasses')
200
-			->with($this->user, [IMountProvider::class])
201
-			->willReturn([$providerMount]);
202
-
203
-		$homeMount = $this->createMock(IMountPoint::class);
204
-
205
-		$this->mountProviderCollection->expects($this->once())
206
-			->method('getHomeMountForUser')
207
-			->willReturn($homeMount);
208
-
209
-		$invokedCount = $this->exactly(2);
210
-		$addMountExpectations = [
211
-			1 => $homeMount,
212
-			2 => $providerMount,
213
-		];
214
-		$this->mountManager->expects($invokedCount)
215
-			->method('addMount')
216
-			->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
217
-
218
-		// setup called twice, provider should only be called once
219
-		$this->setupManager->setupForPath($this->path, false);
220
-		$this->setupManager->setupForPath($this->path, false);
221
-	}
222
-
223
-	/**
224
-	 * Tests that setupForPath does not instantiate already set up providers
225
-	 * when called for the same path first with $withChildren set to true
226
-	 * and then set to false.
227
-	 */
228
-	public function testSetupForPathWithChildrenAndNonPartialProviderSkipsAlreadySetupProvider(): void {
229
-		$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42, IMountProvider::class);
230
-		$additionalCachedMount = $this->getCachedMountInfo($this->mountPoint . 'additional/', 43, SetupManagerTestFullMountProvider::class);
231
-
232
-		$this->userMountCache->expects($this->exactly(2))
233
-			->method('getMountForPath')
234
-			->with($this->user, $this->path)
235
-			->willReturn($cachedMount);
236
-
237
-		$this->userMountCache->expects($this->once())->method('registerMounts');
238
-		$this->userMountCache->expects($this->once())->method('getMountsInPath')
239
-			->willReturn([$additionalCachedMount]);
240
-
241
-		$mount = $this->createMock(IMountPoint::class);
242
-		$additionalMount = $this->createMock(IMountPoint::class);
243
-
244
-		$invokedCount = $this->exactly(2);
245
-		$this->mountProviderCollection->expects($invokedCount)
246
-			->method('getUserMountsForProviderClasses')
247
-			->willReturnCallback(function (IUser $userArg, array $providersArg) use (
248
-				$additionalMount,
249
-				$mount,
250
-				$invokedCount) {
251
-				if ($invokedCount->numberOfInvocations() === 1) {
252
-					$providers = [IMountProvider::class];
253
-					$returnMounts = [$mount];
254
-				} else {
255
-					$providers = [SetupManagerTestFullMountProvider::class];
256
-					$returnMounts = [$additionalMount];
257
-				}
258
-
259
-				$this->assertSame($this->user, $userArg);
260
-				$this->assertSame($providersArg, $providers);
261
-
262
-				return $returnMounts;
263
-			});
264
-
265
-		$homeMount = $this->createMock(IMountPoint::class);
266
-
267
-		$this->mountProviderCollection->expects($this->once())
268
-			->method('getHomeMountForUser')
269
-			->willReturn($homeMount);
270
-
271
-		$invokedCount = $this->exactly(3);
272
-		$addMountExpectations = [
273
-			1 => $homeMount,
274
-			2 => $mount,
275
-			3 => $additionalMount,
276
-		];
277
-		$this->mountManager->expects($invokedCount)
278
-			->method('addMount')
279
-			->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
280
-
281
-		// setup called twice, provider should only be called once
282
-		$this->setupManager->setupForPath($this->path, true);
283
-		$this->setupManager->setupForPath($this->path, false);
284
-	}
285
-
286
-	/**
287
-	 * Tests that setupForPath does not set up child mounts again if a parent
288
-	 * was set up with $withChildren set to true.
289
-	 */
290
-	public function testSetupForPathWithChildrenAndPartialProviderSkipsIfParentAlreadySetup():	void {
291
-		$childPath = "{$this->path}/child";
292
-		$childMountPoint = "{$childPath}/";
293
-
294
-		$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
295
-		$cachedChildMount = $this->getCachedMountInfo($childMountPoint, 43);
296
-
297
-		$invokedCount = $this->exactly(3);
298
-		$this->userMountCache->expects($invokedCount)
299
-			->method('getMountForPath')
300
-			->willReturnCallback(function (IUser $userArg, string $pathArg) use (
301
-				$cachedChildMount,
302
-				$cachedMount,
303
-				$childPath,
304
-				$invokedCount) {
305
-				if ($invokedCount->numberOfInvocations() === 1) {
306
-					$expectedPath = $this->path;
307
-					$returnMount = $cachedMount;
308
-				} else {
309
-					$expectedPath = $childPath;
310
-					$returnMount = $cachedChildMount;
311
-				}
312
-
313
-				$this->assertSame($this->user, $userArg);
314
-				$this->assertSame($expectedPath, $pathArg);
315
-
316
-				return $returnMount;
317
-			});
318
-
319
-		$this->userMountCache->expects($this->never())->method('registerMounts');
320
-		$this->userMountCache->expects($this->exactly(2))
321
-			->method('getMountsInPath')
322
-			->willReturn([$cachedChildMount]);
323
-
324
-		$this->fileAccess->expects($this->once())
325
-			->method('getByFileId')
326
-			->with(42)
327
-			->willReturn($this->createMock(CacheEntry::class));
328
-
329
-		$this->fileAccess->expects($this->once())
330
-			->method('getByFileIds')
331
-			->with([43])
332
-			->willReturn([43 => $this->createMock(CacheEntry::class)]);
333
-
334
-		$partialMount = $this->createMock(IMountPoint::class);
335
-		$partialChildMount = $this->createMock(IMountPoint::class);
336
-
337
-		$invokedCount = $this->exactly(2);
338
-		$this->mountProviderCollection->expects($invokedCount)
339
-			->method('getUserMountsFromProviderByPath')
340
-			->willReturnCallback(function (
341
-				string $providerClass,
342
-				string $pathArg,
343
-				bool $forChildren,
344
-				array $mountProviderArgs,
345
-			) use (
346
-				$cachedChildMount,
347
-				$partialMount,
348
-				$partialChildMount,
349
-				$cachedMount,
350
-				$invokedCount
351
-			) {
352
-				$expectedPath = $this->path;
353
-				if ($invokedCount->numberOfInvocations() === 1) {
354
-					// call for the parent
355
-					$expectedCachedMount = $cachedMount;
356
-					$mountPoints = [$partialMount];
357
-					$expectedForChildren = false;
358
-				} else {
359
-					// call for the children
360
-					$expectedCachedMount = $cachedChildMount;
361
-					$mountPoints = [$partialChildMount];
362
-					$expectedForChildren = true;
363
-				}
364
-
365
-				$this->assertSame(SetupManagerTestPartialMountProvider::class, $providerClass);
366
-				$this->assertSame($expectedPath, $pathArg);
367
-				$this->assertSame($expectedForChildren, $forChildren);
368
-				$this->assertCount(1, $mountProviderArgs);
369
-				$this->assertInstanceOf(IMountProviderArgs::class, $mountProviderArgs[0]);
370
-				$this->assertSame($expectedCachedMount, $mountProviderArgs[0]->mountInfo);
371
-
372
-				return $mountPoints;
373
-			});
374
-
375
-		$homeMount = $this->createMock(IMountPoint::class);
376
-
377
-		$this->mountProviderCollection->expects($this->once())
378
-			->method('getHomeMountForUser')
379
-			->willReturn($homeMount);
380
-
381
-		$this->mountProviderCollection->expects($this->never())
382
-			->method('getUserMountsForProviderClasses');
383
-
384
-		$invokedCount = $this->exactly(3);
385
-		$addMountExpectations = [
386
-			1 => $homeMount,
387
-			2 => $partialMount,
388
-			3 => $partialChildMount,
389
-		];
390
-		$this->mountManager->expects($invokedCount)
391
-			->method('addMount')
392
-			->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
393
-
394
-		// once the setup for a path has been done with children, setup for sub
395
-		// paths should not create the same new mounts again
396
-		$this->setupManager->setupForPath($this->path, true);
397
-		$this->setupManager->setupForPath($childPath, false);
398
-		$this->setupManager->setupForPath($childPath, true);
399
-	}
400
-
401
-	/**
402
-	 * Tests that when called twice setupForPath does not set up mounts from
403
-	 * providers implementing IPartialMountProviders or IMountProvider.
404
-	 */
405
-	public function testSetupForPathHandlesPartialAndFullProvidersWithChildren(): void {
406
-		$parentPartialCachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
407
-		$childCachedPartialMount = $this->getCachedMountInfo("{$this->mountPoint}partial/", 43);
408
-		$childCachedFullMount = $this->getCachedMountInfo("{$this->mountPoint}full/", 44, SetupManagerTestFullMountProvider::class);
409
-
410
-		$this->userMountCache->expects($this->exactly(2))
411
-			->method('getMountForPath')
412
-			->with($this->user, $this->path)
413
-			->willReturn($parentPartialCachedMount);
414
-		$this->userMountCache->expects($this->exactly(2))
415
-			->method('getMountsInPath')
416
-			->with($this->user, $this->path)
417
-			->willReturn([$childCachedPartialMount, $childCachedFullMount]);
418
-
419
-		$homeMount = $this->createMock(IMountPoint::class);
420
-		$parentPartialMount = $this->createMock(IMountPoint::class);
421
-		$childPartialMount = $this->createMock(IMountPoint::class);
422
-		$childFullProviderMount = $this->createMock(IMountPoint::class);
423
-
424
-		$this->mountProviderCollection->expects($this->once())
425
-			->method('getHomeMountForUser')
426
-			->willReturn($homeMount);
427
-
428
-		$this->userMountCache->expects($this->once())
429
-			->method('registerMounts')
430
-			->with(
431
-				$this->user, [$childFullProviderMount],
432
-				[SetupManagerTestFullMountProvider::class],
433
-			);
434
-
435
-		$this->fileAccess->expects($this->once())
436
-			->method('getByFileId')
437
-			->with(42)
438
-			->willReturn($this->createMock(CacheEntry::class));
439
-		$childMetadata = $this->createMock(CacheEntry::class);
440
-		$this->fileAccess->expects($this->once())
441
-			->method('getByFileIds')
442
-			->with([43])
443
-			->willReturn([43 => $childMetadata]);
444
-
445
-		$invokedCount = $this->exactly(2);
446
-		$this->mountProviderCollection->expects($invokedCount)
447
-			->method('getUserMountsFromProviderByPath')
448
-			->willReturnCallback(function (string $providerClass, string $pathArg, bool $forChildren, array $mountProviderArgs) use (
449
-				$childCachedPartialMount,
450
-				$childPartialMount,
451
-				$parentPartialMount,
452
-				$parentPartialCachedMount,
453
-				$invokedCount) {
454
-				$expectedPath = $this->path;
455
-				if ($invokedCount->numberOfInvocations() === 1) {
456
-					// call for the parent
457
-					$expectedCachedMount = $parentPartialCachedMount;
458
-					$mountPoints = [$parentPartialMount];
459
-					$expectedForChildren = false;
460
-				} else {
461
-					// call for the children
462
-					$expectedCachedMount = $childCachedPartialMount;
463
-					$mountPoints = [$childPartialMount];
464
-					$expectedForChildren = true;
465
-				}
466
-
467
-				$this->assertSame(SetupManagerTestPartialMountProvider::class, $providerClass);
468
-				$this->assertSame($expectedPath, $pathArg);
469
-				$this->assertSame($expectedForChildren, $forChildren);
470
-				$this->assertCount(1, $mountProviderArgs);
471
-				$this->assertInstanceOf(IMountProviderArgs::class, $mountProviderArgs[0]);
472
-				$this->assertSame($expectedCachedMount, $mountProviderArgs[0]->mountInfo);
473
-
474
-				return $mountPoints;
475
-			});
476
-
477
-		$this->mountProviderCollection->expects($this->once())
478
-			->method('getUserMountsForProviderClasses')
479
-			->with($this->user, [SetupManagerTestFullMountProvider::class])
480
-			->willReturn([$childFullProviderMount]);
481
-
482
-		$invokedCount = $this->exactly(4);
483
-		$addMountExpectations = [
484
-			1 => $homeMount,
485
-			2 => $childFullProviderMount,
486
-			3 => $parentPartialMount,
487
-			4 => $childPartialMount,
488
-		];
489
-		$this->mountManager->expects($invokedCount)
490
-			->method('addMount')
491
-			->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
492
-
493
-		// call twice to test that providers and mounts are only called once
494
-		$this->setupManager->setupForPath($this->path, true);
495
-		$this->setupManager->setupForPath($this->path, true);
496
-	}
497
-
498
-	private function getAddMountCheckCallback(InvokedCount $invokedCount, $expectations): \Closure {
499
-		return function (IMountPoint $actualMount) use ($invokedCount, $expectations) {
500
-			$expectedMount = $expectations[$invokedCount->numberOfInvocations()] ?? null;
501
-			$this->assertSame($expectedMount, $actualMount);
502
-		};
503
-	}
504
-
505
-	public function getCachedMountInfo(string $mountPoint, int $rootId, string $providerClass = SetupManagerTestPartialMountProvider::class): ICachedMountInfo&MockObject {
506
-		$cachedMount = $this->createMock(ICachedMountInfo::class);
507
-		$cachedMount->method('getMountProvider')->willReturn($providerClass);
508
-		$cachedMount->method('getMountPoint')->willReturn($mountPoint);
509
-		$cachedMount->method('getRootId')->willReturn($rootId);
510
-
511
-		return $cachedMount;
512
-	}
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
+    /**
122
+     * Tests that a path is not set up twice for providers implementing
123
+     * IPartialMountProvider in setupForPath.
124
+     */
125
+    public function testSetupForPathWithPartialProviderSkipsAlreadySetupPath(): void {
126
+        $cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
127
+
128
+        $this->userMountCache->expects($this->exactly(2))
129
+            ->method('getMountForPath')
130
+            ->with($this->user, $this->path)
131
+            ->willReturn($cachedMount);
132
+        $this->userMountCache->expects($this->never())->method('registerMounts');
133
+        $this->userMountCache->expects($this->never())->method('getMountsInPath');
134
+
135
+        $this->fileAccess->expects($this->once())
136
+            ->method('getByFileId')
137
+            ->with(42)
138
+            ->willReturn($this->createMock(CacheEntry::class));
139
+
140
+        $partialMount = $this->createMock(IMountPoint::class);
141
+
142
+        $this->mountProviderCollection->expects($this->once())
143
+            ->method('getUserMountsFromProviderByPath')
144
+            ->with(
145
+                SetupManagerTestPartialMountProvider::class,
146
+                $this->path,
147
+                false,
148
+                $this->callback(function (array $args) use ($cachedMount) {
149
+                    $this->assertCount(1, $args);
150
+                    $this->assertInstanceOf(IMountProviderArgs::class, $args[0]);
151
+                    $this->assertSame($cachedMount, $args[0]->mountInfo);
152
+                    return true;
153
+                })
154
+            )
155
+            ->willReturn([$partialMount]);
156
+
157
+        $homeMount = $this->createMock(IMountPoint::class);
158
+
159
+        $this->mountProviderCollection->expects($this->once())
160
+            ->method('getHomeMountForUser')
161
+            ->willReturn($homeMount);
162
+
163
+        $this->mountProviderCollection->expects($this->never())
164
+            ->method('getUserMountsForProviderClasses');
165
+
166
+        $invokedCount = $this->exactly(2);
167
+        $addMountExpectations = [
168
+            1 => $homeMount,
169
+            2 => $partialMount,
170
+        ];
171
+        $this->mountManager->expects($invokedCount)
172
+            ->method('addMount')
173
+            ->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
174
+
175
+        // setup called twice, provider should only be called once
176
+        $this->setupManager->setupForPath($this->path, false);
177
+        $this->setupManager->setupForPath($this->path, false);
178
+    }
179
+
180
+    /**
181
+     * Tests that providers that are not implementing IPartialMountProvider are
182
+     * not set up more than once by setupForPath.
183
+     */
184
+    public function testSetupForPathWithNonPartialProviderSkipsAlreadySetupProvider(): void {
185
+        $cachedMount = $this->getCachedMountInfo($this->mountPoint, 42,
186
+            IMountProvider::class);
187
+
188
+        $this->userMountCache->expects($this->exactly(2))
189
+            ->method('getMountForPath')
190
+            ->with($this->user, $this->path)
191
+            ->willReturn($cachedMount);
192
+
193
+        $this->userMountCache->expects($this->once())->method('registerMounts');
194
+        $this->userMountCache->expects($this->never())->method('getMountsInPath');
195
+
196
+        $providerMount = $this->createMock(IMountPoint::class);
197
+
198
+        $this->mountProviderCollection->expects($this->once())
199
+            ->method('getUserMountsForProviderClasses')
200
+            ->with($this->user, [IMountProvider::class])
201
+            ->willReturn([$providerMount]);
202
+
203
+        $homeMount = $this->createMock(IMountPoint::class);
204
+
205
+        $this->mountProviderCollection->expects($this->once())
206
+            ->method('getHomeMountForUser')
207
+            ->willReturn($homeMount);
208
+
209
+        $invokedCount = $this->exactly(2);
210
+        $addMountExpectations = [
211
+            1 => $homeMount,
212
+            2 => $providerMount,
213
+        ];
214
+        $this->mountManager->expects($invokedCount)
215
+            ->method('addMount')
216
+            ->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
217
+
218
+        // setup called twice, provider should only be called once
219
+        $this->setupManager->setupForPath($this->path, false);
220
+        $this->setupManager->setupForPath($this->path, false);
221
+    }
222
+
223
+    /**
224
+     * Tests that setupForPath does not instantiate already set up providers
225
+     * when called for the same path first with $withChildren set to true
226
+     * and then set to false.
227
+     */
228
+    public function testSetupForPathWithChildrenAndNonPartialProviderSkipsAlreadySetupProvider(): void {
229
+        $cachedMount = $this->getCachedMountInfo($this->mountPoint, 42, IMountProvider::class);
230
+        $additionalCachedMount = $this->getCachedMountInfo($this->mountPoint . 'additional/', 43, SetupManagerTestFullMountProvider::class);
231
+
232
+        $this->userMountCache->expects($this->exactly(2))
233
+            ->method('getMountForPath')
234
+            ->with($this->user, $this->path)
235
+            ->willReturn($cachedMount);
236
+
237
+        $this->userMountCache->expects($this->once())->method('registerMounts');
238
+        $this->userMountCache->expects($this->once())->method('getMountsInPath')
239
+            ->willReturn([$additionalCachedMount]);
240
+
241
+        $mount = $this->createMock(IMountPoint::class);
242
+        $additionalMount = $this->createMock(IMountPoint::class);
243
+
244
+        $invokedCount = $this->exactly(2);
245
+        $this->mountProviderCollection->expects($invokedCount)
246
+            ->method('getUserMountsForProviderClasses')
247
+            ->willReturnCallback(function (IUser $userArg, array $providersArg) use (
248
+                $additionalMount,
249
+                $mount,
250
+                $invokedCount) {
251
+                if ($invokedCount->numberOfInvocations() === 1) {
252
+                    $providers = [IMountProvider::class];
253
+                    $returnMounts = [$mount];
254
+                } else {
255
+                    $providers = [SetupManagerTestFullMountProvider::class];
256
+                    $returnMounts = [$additionalMount];
257
+                }
258
+
259
+                $this->assertSame($this->user, $userArg);
260
+                $this->assertSame($providersArg, $providers);
261
+
262
+                return $returnMounts;
263
+            });
264
+
265
+        $homeMount = $this->createMock(IMountPoint::class);
266
+
267
+        $this->mountProviderCollection->expects($this->once())
268
+            ->method('getHomeMountForUser')
269
+            ->willReturn($homeMount);
270
+
271
+        $invokedCount = $this->exactly(3);
272
+        $addMountExpectations = [
273
+            1 => $homeMount,
274
+            2 => $mount,
275
+            3 => $additionalMount,
276
+        ];
277
+        $this->mountManager->expects($invokedCount)
278
+            ->method('addMount')
279
+            ->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
280
+
281
+        // setup called twice, provider should only be called once
282
+        $this->setupManager->setupForPath($this->path, true);
283
+        $this->setupManager->setupForPath($this->path, false);
284
+    }
285
+
286
+    /**
287
+     * Tests that setupForPath does not set up child mounts again if a parent
288
+     * was set up with $withChildren set to true.
289
+     */
290
+    public function testSetupForPathWithChildrenAndPartialProviderSkipsIfParentAlreadySetup():	void {
291
+        $childPath = "{$this->path}/child";
292
+        $childMountPoint = "{$childPath}/";
293
+
294
+        $cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
295
+        $cachedChildMount = $this->getCachedMountInfo($childMountPoint, 43);
296
+
297
+        $invokedCount = $this->exactly(3);
298
+        $this->userMountCache->expects($invokedCount)
299
+            ->method('getMountForPath')
300
+            ->willReturnCallback(function (IUser $userArg, string $pathArg) use (
301
+                $cachedChildMount,
302
+                $cachedMount,
303
+                $childPath,
304
+                $invokedCount) {
305
+                if ($invokedCount->numberOfInvocations() === 1) {
306
+                    $expectedPath = $this->path;
307
+                    $returnMount = $cachedMount;
308
+                } else {
309
+                    $expectedPath = $childPath;
310
+                    $returnMount = $cachedChildMount;
311
+                }
312
+
313
+                $this->assertSame($this->user, $userArg);
314
+                $this->assertSame($expectedPath, $pathArg);
315
+
316
+                return $returnMount;
317
+            });
318
+
319
+        $this->userMountCache->expects($this->never())->method('registerMounts');
320
+        $this->userMountCache->expects($this->exactly(2))
321
+            ->method('getMountsInPath')
322
+            ->willReturn([$cachedChildMount]);
323
+
324
+        $this->fileAccess->expects($this->once())
325
+            ->method('getByFileId')
326
+            ->with(42)
327
+            ->willReturn($this->createMock(CacheEntry::class));
328
+
329
+        $this->fileAccess->expects($this->once())
330
+            ->method('getByFileIds')
331
+            ->with([43])
332
+            ->willReturn([43 => $this->createMock(CacheEntry::class)]);
333
+
334
+        $partialMount = $this->createMock(IMountPoint::class);
335
+        $partialChildMount = $this->createMock(IMountPoint::class);
336
+
337
+        $invokedCount = $this->exactly(2);
338
+        $this->mountProviderCollection->expects($invokedCount)
339
+            ->method('getUserMountsFromProviderByPath')
340
+            ->willReturnCallback(function (
341
+                string $providerClass,
342
+                string $pathArg,
343
+                bool $forChildren,
344
+                array $mountProviderArgs,
345
+            ) use (
346
+                $cachedChildMount,
347
+                $partialMount,
348
+                $partialChildMount,
349
+                $cachedMount,
350
+                $invokedCount
351
+            ) {
352
+                $expectedPath = $this->path;
353
+                if ($invokedCount->numberOfInvocations() === 1) {
354
+                    // call for the parent
355
+                    $expectedCachedMount = $cachedMount;
356
+                    $mountPoints = [$partialMount];
357
+                    $expectedForChildren = false;
358
+                } else {
359
+                    // call for the children
360
+                    $expectedCachedMount = $cachedChildMount;
361
+                    $mountPoints = [$partialChildMount];
362
+                    $expectedForChildren = true;
363
+                }
364
+
365
+                $this->assertSame(SetupManagerTestPartialMountProvider::class, $providerClass);
366
+                $this->assertSame($expectedPath, $pathArg);
367
+                $this->assertSame($expectedForChildren, $forChildren);
368
+                $this->assertCount(1, $mountProviderArgs);
369
+                $this->assertInstanceOf(IMountProviderArgs::class, $mountProviderArgs[0]);
370
+                $this->assertSame($expectedCachedMount, $mountProviderArgs[0]->mountInfo);
371
+
372
+                return $mountPoints;
373
+            });
374
+
375
+        $homeMount = $this->createMock(IMountPoint::class);
376
+
377
+        $this->mountProviderCollection->expects($this->once())
378
+            ->method('getHomeMountForUser')
379
+            ->willReturn($homeMount);
380
+
381
+        $this->mountProviderCollection->expects($this->never())
382
+            ->method('getUserMountsForProviderClasses');
383
+
384
+        $invokedCount = $this->exactly(3);
385
+        $addMountExpectations = [
386
+            1 => $homeMount,
387
+            2 => $partialMount,
388
+            3 => $partialChildMount,
389
+        ];
390
+        $this->mountManager->expects($invokedCount)
391
+            ->method('addMount')
392
+            ->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
393
+
394
+        // once the setup for a path has been done with children, setup for sub
395
+        // paths should not create the same new mounts again
396
+        $this->setupManager->setupForPath($this->path, true);
397
+        $this->setupManager->setupForPath($childPath, false);
398
+        $this->setupManager->setupForPath($childPath, true);
399
+    }
400
+
401
+    /**
402
+     * Tests that when called twice setupForPath does not set up mounts from
403
+     * providers implementing IPartialMountProviders or IMountProvider.
404
+     */
405
+    public function testSetupForPathHandlesPartialAndFullProvidersWithChildren(): void {
406
+        $parentPartialCachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
407
+        $childCachedPartialMount = $this->getCachedMountInfo("{$this->mountPoint}partial/", 43);
408
+        $childCachedFullMount = $this->getCachedMountInfo("{$this->mountPoint}full/", 44, SetupManagerTestFullMountProvider::class);
409
+
410
+        $this->userMountCache->expects($this->exactly(2))
411
+            ->method('getMountForPath')
412
+            ->with($this->user, $this->path)
413
+            ->willReturn($parentPartialCachedMount);
414
+        $this->userMountCache->expects($this->exactly(2))
415
+            ->method('getMountsInPath')
416
+            ->with($this->user, $this->path)
417
+            ->willReturn([$childCachedPartialMount, $childCachedFullMount]);
418
+
419
+        $homeMount = $this->createMock(IMountPoint::class);
420
+        $parentPartialMount = $this->createMock(IMountPoint::class);
421
+        $childPartialMount = $this->createMock(IMountPoint::class);
422
+        $childFullProviderMount = $this->createMock(IMountPoint::class);
423
+
424
+        $this->mountProviderCollection->expects($this->once())
425
+            ->method('getHomeMountForUser')
426
+            ->willReturn($homeMount);
427
+
428
+        $this->userMountCache->expects($this->once())
429
+            ->method('registerMounts')
430
+            ->with(
431
+                $this->user, [$childFullProviderMount],
432
+                [SetupManagerTestFullMountProvider::class],
433
+            );
434
+
435
+        $this->fileAccess->expects($this->once())
436
+            ->method('getByFileId')
437
+            ->with(42)
438
+            ->willReturn($this->createMock(CacheEntry::class));
439
+        $childMetadata = $this->createMock(CacheEntry::class);
440
+        $this->fileAccess->expects($this->once())
441
+            ->method('getByFileIds')
442
+            ->with([43])
443
+            ->willReturn([43 => $childMetadata]);
444
+
445
+        $invokedCount = $this->exactly(2);
446
+        $this->mountProviderCollection->expects($invokedCount)
447
+            ->method('getUserMountsFromProviderByPath')
448
+            ->willReturnCallback(function (string $providerClass, string $pathArg, bool $forChildren, array $mountProviderArgs) use (
449
+                $childCachedPartialMount,
450
+                $childPartialMount,
451
+                $parentPartialMount,
452
+                $parentPartialCachedMount,
453
+                $invokedCount) {
454
+                $expectedPath = $this->path;
455
+                if ($invokedCount->numberOfInvocations() === 1) {
456
+                    // call for the parent
457
+                    $expectedCachedMount = $parentPartialCachedMount;
458
+                    $mountPoints = [$parentPartialMount];
459
+                    $expectedForChildren = false;
460
+                } else {
461
+                    // call for the children
462
+                    $expectedCachedMount = $childCachedPartialMount;
463
+                    $mountPoints = [$childPartialMount];
464
+                    $expectedForChildren = true;
465
+                }
466
+
467
+                $this->assertSame(SetupManagerTestPartialMountProvider::class, $providerClass);
468
+                $this->assertSame($expectedPath, $pathArg);
469
+                $this->assertSame($expectedForChildren, $forChildren);
470
+                $this->assertCount(1, $mountProviderArgs);
471
+                $this->assertInstanceOf(IMountProviderArgs::class, $mountProviderArgs[0]);
472
+                $this->assertSame($expectedCachedMount, $mountProviderArgs[0]->mountInfo);
473
+
474
+                return $mountPoints;
475
+            });
476
+
477
+        $this->mountProviderCollection->expects($this->once())
478
+            ->method('getUserMountsForProviderClasses')
479
+            ->with($this->user, [SetupManagerTestFullMountProvider::class])
480
+            ->willReturn([$childFullProviderMount]);
481
+
482
+        $invokedCount = $this->exactly(4);
483
+        $addMountExpectations = [
484
+            1 => $homeMount,
485
+            2 => $childFullProviderMount,
486
+            3 => $parentPartialMount,
487
+            4 => $childPartialMount,
488
+        ];
489
+        $this->mountManager->expects($invokedCount)
490
+            ->method('addMount')
491
+            ->willReturnCallback($this->getAddMountCheckCallback($invokedCount, $addMountExpectations));
492
+
493
+        // call twice to test that providers and mounts are only called once
494
+        $this->setupManager->setupForPath($this->path, true);
495
+        $this->setupManager->setupForPath($this->path, true);
496
+    }
497
+
498
+    private function getAddMountCheckCallback(InvokedCount $invokedCount, $expectations): \Closure {
499
+        return function (IMountPoint $actualMount) use ($invokedCount, $expectations) {
500
+            $expectedMount = $expectations[$invokedCount->numberOfInvocations()] ?? null;
501
+            $this->assertSame($expectedMount, $actualMount);
502
+        };
503
+    }
504
+
505
+    public function getCachedMountInfo(string $mountPoint, int $rootId, string $providerClass = SetupManagerTestPartialMountProvider::class): ICachedMountInfo&MockObject {
506
+        $cachedMount = $this->createMock(ICachedMountInfo::class);
507
+        $cachedMount->method('getMountProvider')->willReturn($providerClass);
508
+        $cachedMount->method('getMountPoint')->willReturn($mountPoint);
509
+        $cachedMount->method('getRootId')->willReturn($rootId);
510
+
511
+        return $cachedMount;
512
+    }
513 513
 }
514 514
 
515 515
 class SetupManagerTestPartialMountProvider implements IPartialMountProvider {
516
-	public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
517
-		return [];
518
-	}
516
+    public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
517
+        return [];
518
+    }
519 519
 
520
-	public function getMountsForPath(string $path, bool $forChildren, array $mountProviderArgs, IStorageFactory $loader): array {
521
-		return [];
522
-	}
520
+    public function getMountsForPath(string $path, bool $forChildren, array $mountProviderArgs, IStorageFactory $loader): array {
521
+        return [];
522
+    }
523 523
 }
524 524
 
525 525
 class SetupManagerTestFullMountProvider implements IMountProvider {
526
-	public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
527
-		return [];
528
-	}
526
+    public function getMountsForUser(IUser $user, IStorageFactory $loader): array {
527
+        return [];
528
+    }
529 529
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -145,7 +145,7 @@  discard block
 block discarded – undo
145 145
 				SetupManagerTestPartialMountProvider::class,
146 146
 				$this->path,
147 147
 				false,
148
-				$this->callback(function (array $args) use ($cachedMount) {
148
+				$this->callback(function(array $args) use ($cachedMount) {
149 149
 					$this->assertCount(1, $args);
150 150
 					$this->assertInstanceOf(IMountProviderArgs::class, $args[0]);
151 151
 					$this->assertSame($cachedMount, $args[0]->mountInfo);
@@ -227,7 +227,7 @@  discard block
 block discarded – undo
227 227
 	 */
228 228
 	public function testSetupForPathWithChildrenAndNonPartialProviderSkipsAlreadySetupProvider(): void {
229 229
 		$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42, IMountProvider::class);
230
-		$additionalCachedMount = $this->getCachedMountInfo($this->mountPoint . 'additional/', 43, SetupManagerTestFullMountProvider::class);
230
+		$additionalCachedMount = $this->getCachedMountInfo($this->mountPoint.'additional/', 43, SetupManagerTestFullMountProvider::class);
231 231
 
232 232
 		$this->userMountCache->expects($this->exactly(2))
233 233
 			->method('getMountForPath')
@@ -244,7 +244,7 @@  discard block
 block discarded – undo
244 244
 		$invokedCount = $this->exactly(2);
245 245
 		$this->mountProviderCollection->expects($invokedCount)
246 246
 			->method('getUserMountsForProviderClasses')
247
-			->willReturnCallback(function (IUser $userArg, array $providersArg) use (
247
+			->willReturnCallback(function(IUser $userArg, array $providersArg) use (
248 248
 				$additionalMount,
249 249
 				$mount,
250 250
 				$invokedCount) {
@@ -297,7 +297,7 @@  discard block
 block discarded – undo
297 297
 		$invokedCount = $this->exactly(3);
298 298
 		$this->userMountCache->expects($invokedCount)
299 299
 			->method('getMountForPath')
300
-			->willReturnCallback(function (IUser $userArg, string $pathArg) use (
300
+			->willReturnCallback(function(IUser $userArg, string $pathArg) use (
301 301
 				$cachedChildMount,
302 302
 				$cachedMount,
303 303
 				$childPath,
@@ -337,7 +337,7 @@  discard block
 block discarded – undo
337 337
 		$invokedCount = $this->exactly(2);
338 338
 		$this->mountProviderCollection->expects($invokedCount)
339 339
 			->method('getUserMountsFromProviderByPath')
340
-			->willReturnCallback(function (
340
+			->willReturnCallback(function(
341 341
 				string $providerClass,
342 342
 				string $pathArg,
343 343
 				bool $forChildren,
@@ -445,7 +445,7 @@  discard block
 block discarded – undo
445 445
 		$invokedCount = $this->exactly(2);
446 446
 		$this->mountProviderCollection->expects($invokedCount)
447 447
 			->method('getUserMountsFromProviderByPath')
448
-			->willReturnCallback(function (string $providerClass, string $pathArg, bool $forChildren, array $mountProviderArgs) use (
448
+			->willReturnCallback(function(string $providerClass, string $pathArg, bool $forChildren, array $mountProviderArgs) use (
449 449
 				$childCachedPartialMount,
450 450
 				$childPartialMount,
451 451
 				$parentPartialMount,
@@ -496,7 +496,7 @@  discard block
 block discarded – undo
496 496
 	}
497 497
 
498 498
 	private function getAddMountCheckCallback(InvokedCount $invokedCount, $expectations): \Closure {
499
-		return function (IMountPoint $actualMount) use ($invokedCount, $expectations) {
499
+		return function(IMountPoint $actualMount) use ($invokedCount, $expectations) {
500 500
 			$expectedMount = $expectations[$invokedCount->numberOfInvocations()] ?? null;
501 501
 			$this->assertSame($expectedMount, $actualMount);
502 502
 		};
Please login to merge, or discard this patch.