Passed
Push — master ( fda9d4...36835b )
by Robin
15:18 queued 12s
created
lib/private/Files/SetupManager.php 1 patch
Indentation   +541 added lines, -541 removed lines patch added patch discarded remove patch
@@ -63,547 +63,547 @@
 block discarded – undo
63 63
 use Psr\Log\LoggerInterface;
64 64
 
65 65
 class SetupManager {
66
-	private bool $rootSetup = false;
67
-	private IEventLogger $eventLogger;
68
-	private MountProviderCollection $mountProviderCollection;
69
-	private IMountManager $mountManager;
70
-	private IUserManager $userManager;
71
-	// List of users for which at least one mount is setup
72
-	private array $setupUsers = [];
73
-	// List of users for which all mounts are setup
74
-	private array $setupUsersComplete = [];
75
-	/** @var array<string, string[]> */
76
-	private array $setupUserMountProviders = [];
77
-	private IEventDispatcher $eventDispatcher;
78
-	private IUserMountCache $userMountCache;
79
-	private ILockdownManager $lockdownManager;
80
-	private IUserSession $userSession;
81
-	private ICache $cache;
82
-	private LoggerInterface $logger;
83
-	private IConfig $config;
84
-	private bool $listeningForProviders;
85
-	private array $fullSetupRequired = [];
86
-	private bool $setupBuiltinWrappersDone = false;
87
-
88
-	public function __construct(
89
-		IEventLogger $eventLogger,
90
-		MountProviderCollection $mountProviderCollection,
91
-		IMountManager $mountManager,
92
-		IUserManager $userManager,
93
-		IEventDispatcher $eventDispatcher,
94
-		IUserMountCache $userMountCache,
95
-		ILockdownManager $lockdownManager,
96
-		IUserSession $userSession,
97
-		ICacheFactory $cacheFactory,
98
-		LoggerInterface $logger,
99
-		IConfig $config
100
-	) {
101
-		$this->eventLogger = $eventLogger;
102
-		$this->mountProviderCollection = $mountProviderCollection;
103
-		$this->mountManager = $mountManager;
104
-		$this->userManager = $userManager;
105
-		$this->eventDispatcher = $eventDispatcher;
106
-		$this->userMountCache = $userMountCache;
107
-		$this->lockdownManager = $lockdownManager;
108
-		$this->logger = $logger;
109
-		$this->userSession = $userSession;
110
-		$this->cache = $cacheFactory->createDistributed('setupmanager::');
111
-		$this->listeningForProviders = false;
112
-		$this->config = $config;
113
-
114
-		$this->setupListeners();
115
-	}
116
-
117
-	private function isSetupStarted(IUser $user): bool {
118
-		return in_array($user->getUID(), $this->setupUsers, true);
119
-	}
120
-
121
-	public function isSetupComplete(IUser $user): bool {
122
-		return in_array($user->getUID(), $this->setupUsersComplete, true);
123
-	}
124
-
125
-	private function setupBuiltinWrappers() {
126
-		if ($this->setupBuiltinWrappersDone) {
127
-			return;
128
-		}
129
-		$this->setupBuiltinWrappersDone = true;
130
-
131
-		// load all filesystem apps before, so no setup-hook gets lost
132
-		OC_App::loadApps(['filesystem']);
133
-		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
134
-
135
-		Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
136
-			if ($storage->instanceOfStorage(Common::class)) {
137
-				$storage->setMountOptions($mount->getOptions());
138
-			}
139
-			return $storage;
140
-		});
141
-
142
-		Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
143
-			if (!$mount->getOption('enable_sharing', true)) {
144
-				return new PermissionsMask([
145
-					'storage' => $storage,
146
-					'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
147
-				]);
148
-			}
149
-			return $storage;
150
-		});
151
-
152
-		// install storage availability wrapper, before most other wrappers
153
-		Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage) {
154
-			if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
155
-				return new Availability(['storage' => $storage]);
156
-			}
157
-			return $storage;
158
-		});
159
-
160
-		Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
161
-			if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
162
-				return new Encoding(['storage' => $storage]);
163
-			}
164
-			return $storage;
165
-		});
166
-
167
-		Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) {
168
-			// set up quota for home storages, even for other users
169
-			// which can happen when using sharing
170
-
171
-			/**
172
-			 * @var Storage $storage
173
-			 */
174
-			if ($storage->instanceOfStorage(HomeObjectStoreStorage::class) || $storage->instanceOfStorage(Home::class)) {
175
-				if (is_object($storage->getUser())) {
176
-					$user = $storage->getUser();
177
-					return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
178
-						return OC_Util::getUserQuota($user);
179
-					}, 'root' => 'files']);
180
-				}
181
-			}
182
-
183
-			return $storage;
184
-		});
185
-
186
-		Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
187
-			/*
66
+    private bool $rootSetup = false;
67
+    private IEventLogger $eventLogger;
68
+    private MountProviderCollection $mountProviderCollection;
69
+    private IMountManager $mountManager;
70
+    private IUserManager $userManager;
71
+    // List of users for which at least one mount is setup
72
+    private array $setupUsers = [];
73
+    // List of users for which all mounts are setup
74
+    private array $setupUsersComplete = [];
75
+    /** @var array<string, string[]> */
76
+    private array $setupUserMountProviders = [];
77
+    private IEventDispatcher $eventDispatcher;
78
+    private IUserMountCache $userMountCache;
79
+    private ILockdownManager $lockdownManager;
80
+    private IUserSession $userSession;
81
+    private ICache $cache;
82
+    private LoggerInterface $logger;
83
+    private IConfig $config;
84
+    private bool $listeningForProviders;
85
+    private array $fullSetupRequired = [];
86
+    private bool $setupBuiltinWrappersDone = false;
87
+
88
+    public function __construct(
89
+        IEventLogger $eventLogger,
90
+        MountProviderCollection $mountProviderCollection,
91
+        IMountManager $mountManager,
92
+        IUserManager $userManager,
93
+        IEventDispatcher $eventDispatcher,
94
+        IUserMountCache $userMountCache,
95
+        ILockdownManager $lockdownManager,
96
+        IUserSession $userSession,
97
+        ICacheFactory $cacheFactory,
98
+        LoggerInterface $logger,
99
+        IConfig $config
100
+    ) {
101
+        $this->eventLogger = $eventLogger;
102
+        $this->mountProviderCollection = $mountProviderCollection;
103
+        $this->mountManager = $mountManager;
104
+        $this->userManager = $userManager;
105
+        $this->eventDispatcher = $eventDispatcher;
106
+        $this->userMountCache = $userMountCache;
107
+        $this->lockdownManager = $lockdownManager;
108
+        $this->logger = $logger;
109
+        $this->userSession = $userSession;
110
+        $this->cache = $cacheFactory->createDistributed('setupmanager::');
111
+        $this->listeningForProviders = false;
112
+        $this->config = $config;
113
+
114
+        $this->setupListeners();
115
+    }
116
+
117
+    private function isSetupStarted(IUser $user): bool {
118
+        return in_array($user->getUID(), $this->setupUsers, true);
119
+    }
120
+
121
+    public function isSetupComplete(IUser $user): bool {
122
+        return in_array($user->getUID(), $this->setupUsersComplete, true);
123
+    }
124
+
125
+    private function setupBuiltinWrappers() {
126
+        if ($this->setupBuiltinWrappersDone) {
127
+            return;
128
+        }
129
+        $this->setupBuiltinWrappersDone = true;
130
+
131
+        // load all filesystem apps before, so no setup-hook gets lost
132
+        OC_App::loadApps(['filesystem']);
133
+        $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
134
+
135
+        Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
136
+            if ($storage->instanceOfStorage(Common::class)) {
137
+                $storage->setMountOptions($mount->getOptions());
138
+            }
139
+            return $storage;
140
+        });
141
+
142
+        Filesystem::addStorageWrapper('enable_sharing', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
143
+            if (!$mount->getOption('enable_sharing', true)) {
144
+                return new PermissionsMask([
145
+                    'storage' => $storage,
146
+                    'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
147
+                ]);
148
+            }
149
+            return $storage;
150
+        });
151
+
152
+        // install storage availability wrapper, before most other wrappers
153
+        Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage) {
154
+            if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) {
155
+                return new Availability(['storage' => $storage]);
156
+            }
157
+            return $storage;
158
+        });
159
+
160
+        Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
161
+            if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
162
+                return new Encoding(['storage' => $storage]);
163
+            }
164
+            return $storage;
165
+        });
166
+
167
+        Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage) {
168
+            // set up quota for home storages, even for other users
169
+            // which can happen when using sharing
170
+
171
+            /**
172
+             * @var Storage $storage
173
+             */
174
+            if ($storage->instanceOfStorage(HomeObjectStoreStorage::class) || $storage->instanceOfStorage(Home::class)) {
175
+                if (is_object($storage->getUser())) {
176
+                    $user = $storage->getUser();
177
+                    return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
178
+                        return OC_Util::getUserQuota($user);
179
+                    }, 'root' => 'files']);
180
+                }
181
+            }
182
+
183
+            return $storage;
184
+        });
185
+
186
+        Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
187
+            /*
188 188
 			 * Do not allow any operations that modify the storage
189 189
 			 */
190
-			if ($mount->getOption('readonly', false)) {
191
-				return new PermissionsMask([
192
-					'storage' => $storage,
193
-					'mask' => Constants::PERMISSION_ALL & ~(
194
-						Constants::PERMISSION_UPDATE |
195
-						Constants::PERMISSION_CREATE |
196
-						Constants::PERMISSION_DELETE
197
-					),
198
-				]);
199
-			}
200
-			return $storage;
201
-		});
202
-
203
-		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
204
-	}
205
-
206
-	/**
207
-	 * Setup the full filesystem for the specified user
208
-	 */
209
-	public function setupForUser(IUser $user): void {
210
-		if ($this->isSetupComplete($user)) {
211
-			return;
212
-		}
213
-		$this->setupUsersComplete[] = $user->getUID();
214
-
215
-		$this->eventLogger->start('fs:setup:user:full', 'Setup full filesystem for user');
216
-
217
-		if (!isset($this->setupUserMountProviders[$user->getUID()])) {
218
-			$this->setupUserMountProviders[$user->getUID()] = [];
219
-		}
220
-
221
-		$previouslySetupProviders = $this->setupUserMountProviders[$user->getUID()];
222
-
223
-		$this->setupForUserWith($user, function () use ($user) {
224
-			$this->mountProviderCollection->addMountForUser($user, $this->mountManager, function (
225
-				IMountProvider $provider
226
-			) use ($user) {
227
-				return !in_array(get_class($provider), $this->setupUserMountProviders[$user->getUID()]);
228
-			});
229
-		});
230
-		$this->afterUserFullySetup($user, $previouslySetupProviders);
231
-		$this->eventLogger->end('fs:setup:user:full');
232
-	}
233
-
234
-	/**
235
-	 * part of the user setup that is run only once per user
236
-	 */
237
-	private function oneTimeUserSetup(IUser $user) {
238
-		if ($this->isSetupStarted($user)) {
239
-			return;
240
-		}
241
-		$this->setupUsers[] = $user->getUID();
242
-
243
-		$this->setupRoot();
244
-
245
-		$this->eventLogger->start('fs:setup:user:onetime', 'Onetime filesystem for user');
246
-
247
-		$this->setupBuiltinWrappers();
248
-
249
-		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
250
-
251
-		OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]);
252
-
253
-		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
254
-
255
-		$userDir = '/' . $user->getUID() . '/files';
256
-
257
-		Filesystem::initInternal($userDir);
258
-
259
-		if ($this->lockdownManager->canAccessFilesystem()) {
260
-			$this->eventLogger->start('fs:setup:user:home', 'Setup home filesystem for user');
261
-			// home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers
262
-			$homeMount = $this->mountProviderCollection->getHomeMountForUser($user);
263
-			$this->mountManager->addMount($homeMount);
264
-
265
-			if ($homeMount->getStorageRootId() === -1) {
266
-				$this->eventLogger->start('fs:setup:user:home:scan', 'Scan home filesystem for user');
267
-				$homeMount->getStorage()->mkdir('');
268
-				$homeMount->getStorage()->getScanner()->scan('');
269
-				$this->eventLogger->end('fs:setup:user:home:scan');
270
-			}
271
-			$this->eventLogger->end('fs:setup:user:home');
272
-		} else {
273
-			$this->mountManager->addMount(new MountPoint(
274
-				new NullStorage([]),
275
-				'/' . $user->getUID()
276
-			));
277
-			$this->mountManager->addMount(new MountPoint(
278
-				new NullStorage([]),
279
-				'/' . $user->getUID() . '/files'
280
-			));
281
-			$this->setupUsersComplete[] = $user->getUID();
282
-		}
283
-
284
-		$this->listenForNewMountProviders();
285
-
286
-		$this->eventLogger->end('fs:setup:user:onetime');
287
-	}
288
-
289
-	/**
290
-	 * Final housekeeping after a user has been fully setup
291
-	 */
292
-	private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void {
293
-		$this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup');
294
-		$userRoot = '/' . $user->getUID() . '/';
295
-		$mounts = $this->mountManager->getAll();
296
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
297
-			return strpos($mount->getMountPoint(), $userRoot) === 0;
298
-		});
299
-		$allProviders = array_map(function (IMountProvider $provider) {
300
-			return get_class($provider);
301
-		}, $this->mountProviderCollection->getProviders());
302
-		$newProviders = array_diff($allProviders, $previouslySetupProviders);
303
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
304
-			return !in_array($mount->getMountProvider(), $previouslySetupProviders);
305
-		});
306
-		$this->userMountCache->registerMounts($user, $mounts, $newProviders);
307
-
308
-		$cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60);
309
-		if ($cacheDuration > 0) {
310
-			$this->cache->set($user->getUID(), true, $cacheDuration);
311
-			$this->fullSetupRequired[$user->getUID()] = false;
312
-		}
313
-		$this->eventLogger->end('fs:setup:user:full:post');
314
-	}
315
-
316
-	/**
317
-	 * @param IUser $user
318
-	 * @param IMountPoint $mounts
319
-	 * @return void
320
-	 * @throws \OCP\HintException
321
-	 * @throws \OC\ServerNotAvailableException
322
-	 */
323
-	private function setupForUserWith(IUser $user, callable $mountCallback): void {
324
-		$this->oneTimeUserSetup($user);
325
-
326
-		if ($this->lockdownManager->canAccessFilesystem()) {
327
-			$mountCallback();
328
-		}
329
-		$this->eventLogger->start('fs:setup:user:post-init-mountpoint', 'post_initMountPoints legacy hook');
330
-		\OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]);
331
-		$this->eventLogger->end('fs:setup:user:post-init-mountpoint');
332
-
333
-		$userDir = '/' . $user->getUID() . '/files';
334
-		$this->eventLogger->start('fs:setup:user:setup-hook', 'setup legacy hook');
335
-		OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]);
336
-		$this->eventLogger->end('fs:setup:user:setup-hook');
337
-	}
338
-
339
-	/**
340
-	 * Set up the root filesystem
341
-	 */
342
-	public function setupRoot(): void {
343
-		//setting up the filesystem twice can only lead to trouble
344
-		if ($this->rootSetup) {
345
-			return;
346
-		}
347
-		$this->rootSetup = true;
348
-
349
-		$this->eventLogger->start('fs:setup:root', 'Setup root filesystem');
350
-
351
-		$this->setupBuiltinWrappers();
352
-
353
-		$rootMounts = $this->mountProviderCollection->getRootMounts();
354
-		foreach ($rootMounts as $rootMountProvider) {
355
-			$this->mountManager->addMount($rootMountProvider);
356
-		}
357
-
358
-		$this->eventLogger->end('fs:setup:root');
359
-	}
360
-
361
-	/**
362
-	 * Get the user to setup for a path or `null` if the root needs to be setup
363
-	 *
364
-	 * @param string $path
365
-	 * @return IUser|null
366
-	 */
367
-	private function getUserForPath(string $path) {
368
-		if (strpos($path, '/__groupfolders') === 0) {
369
-			return null;
370
-		} elseif (substr_count($path, '/') < 2) {
371
-			if ($user = $this->userSession->getUser()) {
372
-				return $user;
373
-			} else {
374
-				return null;
375
-			}
376
-		} elseif (strpos($path, '/appdata_' . \OC_Util::getInstanceId()) === 0 || strpos($path, '/files_external/') === 0) {
377
-			return null;
378
-		} else {
379
-			[, $userId] = explode('/', $path);
380
-		}
381
-
382
-		return $this->userManager->get($userId);
383
-	}
384
-
385
-	/**
386
-	 * Set up the filesystem for the specified path
387
-	 */
388
-	public function setupForPath(string $path, bool $includeChildren = false): void {
389
-		$user = $this->getUserForPath($path);
390
-		if (!$user) {
391
-			$this->setupRoot();
392
-			return;
393
-		}
394
-
395
-		if ($this->isSetupComplete($user)) {
396
-			return;
397
-		}
398
-
399
-		if ($this->fullSetupRequired($user)) {
400
-			$this->setupForUser($user);
401
-			return;
402
-		}
403
-
404
-		// for the user's home folder, and includes children we need everything always
405
-		if (rtrim($path) === "/" . $user->getUID() . "/files" && $includeChildren) {
406
-			$this->setupForUser($user);
407
-			return;
408
-		}
409
-
410
-		if (!isset($this->setupUserMountProviders[$user->getUID()])) {
411
-			$this->setupUserMountProviders[$user->getUID()] = [];
412
-		}
413
-		$setupProviders = &$this->setupUserMountProviders[$user->getUID()];
414
-		$currentProviders = [];
415
-
416
-		try {
417
-			$cachedMount = $this->userMountCache->getMountForPath($user, $path);
418
-		} catch (NotFoundException $e) {
419
-			$this->setupForUser($user);
420
-			return;
421
-		}
422
-
423
-		$this->oneTimeUserSetup($user);
424
-
425
-		$this->eventLogger->start('fs:setup:user:path', "Setup $path filesystem for user");
426
-		$this->eventLogger->start('fs:setup:user:path:find', "Find mountpoint for $path");
427
-
428
-		$mounts = [];
429
-		if (!in_array($cachedMount->getMountProvider(), $setupProviders)) {
430
-			$currentProviders[] = $cachedMount->getMountProvider();
431
-			if ($cachedMount->getMountProvider()) {
432
-				$setupProviders[] = $cachedMount->getMountProvider();
433
-				$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]);
434
-			} else {
435
-				$this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup");
436
-				$this->eventLogger->end('fs:setup:user:path:find');
437
-				$this->setupForUser($user);
438
-				$this->eventLogger->end('fs:setup:user:path');
439
-				return;
440
-			}
441
-		}
442
-
443
-		if ($includeChildren) {
444
-			$subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
445
-			$this->eventLogger->end('fs:setup:user:path:find');
446
-
447
-			$needsFullSetup = array_reduce($subCachedMounts, function (bool $needsFullSetup, ICachedMountInfo $cachedMountInfo) {
448
-				return $needsFullSetup || $cachedMountInfo->getMountProvider() === '';
449
-			}, false);
450
-
451
-			if ($needsFullSetup) {
452
-				$this->logger->debug("mount has no provider set, performing full setup");
453
-				$this->setupForUser($user);
454
-				$this->eventLogger->end('fs:setup:user:path');
455
-				return;
456
-			} else {
457
-				foreach ($subCachedMounts as $cachedMount) {
458
-					if (!in_array($cachedMount->getMountProvider(), $setupProviders)) {
459
-						$currentProviders[] = $cachedMount->getMountProvider();
460
-						$setupProviders[] = $cachedMount->getMountProvider();
461
-						$mounts = array_merge($mounts, $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]));
462
-					}
463
-				}
464
-			}
465
-		} else {
466
-			$this->eventLogger->end('fs:setup:user:path:find');
467
-		}
468
-
469
-		if (count($mounts)) {
470
-			$this->userMountCache->registerMounts($user, $mounts, $currentProviders);
471
-			$this->setupForUserWith($user, function () use ($mounts) {
472
-				array_walk($mounts, [$this->mountManager, 'addMount']);
473
-			});
474
-		} elseif (!$this->isSetupStarted($user)) {
475
-			$this->oneTimeUserSetup($user);
476
-		}
477
-		$this->eventLogger->end('fs:setup:user:path');
478
-	}
479
-
480
-	private function fullSetupRequired(IUser $user): bool {
481
-		// we perform a "cached" setup only after having done the full setup recently
482
-		// this is also used to trigger a full setup after handling events that are likely
483
-		// to change the available mounts
484
-		if (!isset($this->fullSetupRequired[$user->getUID()])) {
485
-			$this->fullSetupRequired[$user->getUID()] = !$this->cache->get($user->getUID());
486
-		}
487
-		return $this->fullSetupRequired[$user->getUID()];
488
-	}
489
-
490
-	/**
491
-	 * @param string $path
492
-	 * @param string[] $providers
493
-	 */
494
-	public function setupForProvider(string $path, array $providers): void {
495
-		$user = $this->getUserForPath($path);
496
-		if (!$user) {
497
-			$this->setupRoot();
498
-			return;
499
-		}
500
-
501
-		if ($this->isSetupComplete($user)) {
502
-			return;
503
-		}
504
-
505
-		if ($this->fullSetupRequired($user)) {
506
-			$this->setupForUser($user);
507
-			return;
508
-		}
509
-
510
-		$this->eventLogger->start('fs:setup:user:providers', "Setup filesystem for " . implode(', ', $providers));
511
-
512
-		$this->oneTimeUserSetup($user);
513
-
514
-		// home providers are always used
515
-		$providers = array_filter($providers, function (string $provider) {
516
-			return !is_subclass_of($provider, IHomeMountProvider::class);
517
-		});
518
-
519
-		if (in_array('', $providers)) {
520
-			$this->setupForUser($user);
521
-			return;
522
-		}
523
-		$setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
524
-
525
-		$providers = array_diff($providers, $setupProviders);
526
-		if (count($providers) === 0) {
527
-			if (!$this->isSetupStarted($user)) {
528
-				$this->oneTimeUserSetup($user);
529
-			}
530
-			$this->eventLogger->end('fs:setup:user:providers');
531
-			return;
532
-		} else {
533
-			$this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
534
-			$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
535
-		}
536
-
537
-		$this->userMountCache->registerMounts($user, $mounts, $providers);
538
-		$this->setupForUserWith($user, function () use ($mounts) {
539
-			array_walk($mounts, [$this->mountManager, 'addMount']);
540
-		});
541
-		$this->eventLogger->end('fs:setup:user:providers');
542
-	}
543
-
544
-	public function tearDown() {
545
-		$this->setupUsers = [];
546
-		$this->setupUsersComplete = [];
547
-		$this->setupUserMountProviders = [];
548
-		$this->fullSetupRequired = [];
549
-		$this->rootSetup = false;
550
-		$this->mountManager->clear();
551
-		$this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
552
-	}
553
-
554
-	/**
555
-	 * Get mounts from mount providers that are registered after setup
556
-	 */
557
-	private function listenForNewMountProviders() {
558
-		if (!$this->listeningForProviders) {
559
-			$this->listeningForProviders = true;
560
-			$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
561
-				IMountProvider $provider
562
-			) {
563
-				foreach ($this->setupUsers as $userId) {
564
-					$user = $this->userManager->get($userId);
565
-					if ($user) {
566
-						$mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
567
-						array_walk($mounts, [$this->mountManager, 'addMount']);
568
-					}
569
-				}
570
-			});
571
-		}
572
-	}
573
-
574
-	private function setupListeners() {
575
-		// note that this event handling is intentionally pessimistic
576
-		// clearing the cache to often is better than not enough
577
-
578
-		$this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
579
-			$this->cache->remove($event->getUser()->getUID());
580
-		});
581
-		$this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
582
-			$this->cache->remove($event->getUser()->getUID());
583
-		});
584
-		$this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
585
-			$this->cache->remove($event->getShare()->getSharedWith());
586
-		});
587
-		$this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event
588
-		) {
589
-			if ($user = $event->getUser()) {
590
-				$this->cache->remove($user->getUID());
591
-			} else {
592
-				$this->cache->clear();
593
-			}
594
-		});
595
-
596
-		$genericEvents = [
597
-			'OCA\Circles\Events\CreatingCircleEvent',
598
-			'OCA\Circles\Events\DestroyingCircleEvent',
599
-			'OCA\Circles\Events\AddingCircleMemberEvent',
600
-			'OCA\Circles\Events\RemovingCircleMemberEvent',
601
-		];
602
-
603
-		foreach ($genericEvents as $genericEvent) {
604
-			$this->eventDispatcher->addListener($genericEvent, function ($event) {
605
-				$this->cache->clear();
606
-			});
607
-		}
608
-	}
190
+            if ($mount->getOption('readonly', false)) {
191
+                return new PermissionsMask([
192
+                    'storage' => $storage,
193
+                    'mask' => Constants::PERMISSION_ALL & ~(
194
+                        Constants::PERMISSION_UPDATE |
195
+                        Constants::PERMISSION_CREATE |
196
+                        Constants::PERMISSION_DELETE
197
+                    ),
198
+                ]);
199
+            }
200
+            return $storage;
201
+        });
202
+
203
+        Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
204
+    }
205
+
206
+    /**
207
+     * Setup the full filesystem for the specified user
208
+     */
209
+    public function setupForUser(IUser $user): void {
210
+        if ($this->isSetupComplete($user)) {
211
+            return;
212
+        }
213
+        $this->setupUsersComplete[] = $user->getUID();
214
+
215
+        $this->eventLogger->start('fs:setup:user:full', 'Setup full filesystem for user');
216
+
217
+        if (!isset($this->setupUserMountProviders[$user->getUID()])) {
218
+            $this->setupUserMountProviders[$user->getUID()] = [];
219
+        }
220
+
221
+        $previouslySetupProviders = $this->setupUserMountProviders[$user->getUID()];
222
+
223
+        $this->setupForUserWith($user, function () use ($user) {
224
+            $this->mountProviderCollection->addMountForUser($user, $this->mountManager, function (
225
+                IMountProvider $provider
226
+            ) use ($user) {
227
+                return !in_array(get_class($provider), $this->setupUserMountProviders[$user->getUID()]);
228
+            });
229
+        });
230
+        $this->afterUserFullySetup($user, $previouslySetupProviders);
231
+        $this->eventLogger->end('fs:setup:user:full');
232
+    }
233
+
234
+    /**
235
+     * part of the user setup that is run only once per user
236
+     */
237
+    private function oneTimeUserSetup(IUser $user) {
238
+        if ($this->isSetupStarted($user)) {
239
+            return;
240
+        }
241
+        $this->setupUsers[] = $user->getUID();
242
+
243
+        $this->setupRoot();
244
+
245
+        $this->eventLogger->start('fs:setup:user:onetime', 'Onetime filesystem for user');
246
+
247
+        $this->setupBuiltinWrappers();
248
+
249
+        $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
250
+
251
+        OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]);
252
+
253
+        Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
254
+
255
+        $userDir = '/' . $user->getUID() . '/files';
256
+
257
+        Filesystem::initInternal($userDir);
258
+
259
+        if ($this->lockdownManager->canAccessFilesystem()) {
260
+            $this->eventLogger->start('fs:setup:user:home', 'Setup home filesystem for user');
261
+            // home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers
262
+            $homeMount = $this->mountProviderCollection->getHomeMountForUser($user);
263
+            $this->mountManager->addMount($homeMount);
264
+
265
+            if ($homeMount->getStorageRootId() === -1) {
266
+                $this->eventLogger->start('fs:setup:user:home:scan', 'Scan home filesystem for user');
267
+                $homeMount->getStorage()->mkdir('');
268
+                $homeMount->getStorage()->getScanner()->scan('');
269
+                $this->eventLogger->end('fs:setup:user:home:scan');
270
+            }
271
+            $this->eventLogger->end('fs:setup:user:home');
272
+        } else {
273
+            $this->mountManager->addMount(new MountPoint(
274
+                new NullStorage([]),
275
+                '/' . $user->getUID()
276
+            ));
277
+            $this->mountManager->addMount(new MountPoint(
278
+                new NullStorage([]),
279
+                '/' . $user->getUID() . '/files'
280
+            ));
281
+            $this->setupUsersComplete[] = $user->getUID();
282
+        }
283
+
284
+        $this->listenForNewMountProviders();
285
+
286
+        $this->eventLogger->end('fs:setup:user:onetime');
287
+    }
288
+
289
+    /**
290
+     * Final housekeeping after a user has been fully setup
291
+     */
292
+    private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void {
293
+        $this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup');
294
+        $userRoot = '/' . $user->getUID() . '/';
295
+        $mounts = $this->mountManager->getAll();
296
+        $mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
297
+            return strpos($mount->getMountPoint(), $userRoot) === 0;
298
+        });
299
+        $allProviders = array_map(function (IMountProvider $provider) {
300
+            return get_class($provider);
301
+        }, $this->mountProviderCollection->getProviders());
302
+        $newProviders = array_diff($allProviders, $previouslySetupProviders);
303
+        $mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
304
+            return !in_array($mount->getMountProvider(), $previouslySetupProviders);
305
+        });
306
+        $this->userMountCache->registerMounts($user, $mounts, $newProviders);
307
+
308
+        $cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60);
309
+        if ($cacheDuration > 0) {
310
+            $this->cache->set($user->getUID(), true, $cacheDuration);
311
+            $this->fullSetupRequired[$user->getUID()] = false;
312
+        }
313
+        $this->eventLogger->end('fs:setup:user:full:post');
314
+    }
315
+
316
+    /**
317
+     * @param IUser $user
318
+     * @param IMountPoint $mounts
319
+     * @return void
320
+     * @throws \OCP\HintException
321
+     * @throws \OC\ServerNotAvailableException
322
+     */
323
+    private function setupForUserWith(IUser $user, callable $mountCallback): void {
324
+        $this->oneTimeUserSetup($user);
325
+
326
+        if ($this->lockdownManager->canAccessFilesystem()) {
327
+            $mountCallback();
328
+        }
329
+        $this->eventLogger->start('fs:setup:user:post-init-mountpoint', 'post_initMountPoints legacy hook');
330
+        \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]);
331
+        $this->eventLogger->end('fs:setup:user:post-init-mountpoint');
332
+
333
+        $userDir = '/' . $user->getUID() . '/files';
334
+        $this->eventLogger->start('fs:setup:user:setup-hook', 'setup legacy hook');
335
+        OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]);
336
+        $this->eventLogger->end('fs:setup:user:setup-hook');
337
+    }
338
+
339
+    /**
340
+     * Set up the root filesystem
341
+     */
342
+    public function setupRoot(): void {
343
+        //setting up the filesystem twice can only lead to trouble
344
+        if ($this->rootSetup) {
345
+            return;
346
+        }
347
+        $this->rootSetup = true;
348
+
349
+        $this->eventLogger->start('fs:setup:root', 'Setup root filesystem');
350
+
351
+        $this->setupBuiltinWrappers();
352
+
353
+        $rootMounts = $this->mountProviderCollection->getRootMounts();
354
+        foreach ($rootMounts as $rootMountProvider) {
355
+            $this->mountManager->addMount($rootMountProvider);
356
+        }
357
+
358
+        $this->eventLogger->end('fs:setup:root');
359
+    }
360
+
361
+    /**
362
+     * Get the user to setup for a path or `null` if the root needs to be setup
363
+     *
364
+     * @param string $path
365
+     * @return IUser|null
366
+     */
367
+    private function getUserForPath(string $path) {
368
+        if (strpos($path, '/__groupfolders') === 0) {
369
+            return null;
370
+        } elseif (substr_count($path, '/') < 2) {
371
+            if ($user = $this->userSession->getUser()) {
372
+                return $user;
373
+            } else {
374
+                return null;
375
+            }
376
+        } elseif (strpos($path, '/appdata_' . \OC_Util::getInstanceId()) === 0 || strpos($path, '/files_external/') === 0) {
377
+            return null;
378
+        } else {
379
+            [, $userId] = explode('/', $path);
380
+        }
381
+
382
+        return $this->userManager->get($userId);
383
+    }
384
+
385
+    /**
386
+     * Set up the filesystem for the specified path
387
+     */
388
+    public function setupForPath(string $path, bool $includeChildren = false): void {
389
+        $user = $this->getUserForPath($path);
390
+        if (!$user) {
391
+            $this->setupRoot();
392
+            return;
393
+        }
394
+
395
+        if ($this->isSetupComplete($user)) {
396
+            return;
397
+        }
398
+
399
+        if ($this->fullSetupRequired($user)) {
400
+            $this->setupForUser($user);
401
+            return;
402
+        }
403
+
404
+        // for the user's home folder, and includes children we need everything always
405
+        if (rtrim($path) === "/" . $user->getUID() . "/files" && $includeChildren) {
406
+            $this->setupForUser($user);
407
+            return;
408
+        }
409
+
410
+        if (!isset($this->setupUserMountProviders[$user->getUID()])) {
411
+            $this->setupUserMountProviders[$user->getUID()] = [];
412
+        }
413
+        $setupProviders = &$this->setupUserMountProviders[$user->getUID()];
414
+        $currentProviders = [];
415
+
416
+        try {
417
+            $cachedMount = $this->userMountCache->getMountForPath($user, $path);
418
+        } catch (NotFoundException $e) {
419
+            $this->setupForUser($user);
420
+            return;
421
+        }
422
+
423
+        $this->oneTimeUserSetup($user);
424
+
425
+        $this->eventLogger->start('fs:setup:user:path', "Setup $path filesystem for user");
426
+        $this->eventLogger->start('fs:setup:user:path:find', "Find mountpoint for $path");
427
+
428
+        $mounts = [];
429
+        if (!in_array($cachedMount->getMountProvider(), $setupProviders)) {
430
+            $currentProviders[] = $cachedMount->getMountProvider();
431
+            if ($cachedMount->getMountProvider()) {
432
+                $setupProviders[] = $cachedMount->getMountProvider();
433
+                $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]);
434
+            } else {
435
+                $this->logger->debug("mount at " . $cachedMount->getMountPoint() . " has no provider set, performing full setup");
436
+                $this->eventLogger->end('fs:setup:user:path:find');
437
+                $this->setupForUser($user);
438
+                $this->eventLogger->end('fs:setup:user:path');
439
+                return;
440
+            }
441
+        }
442
+
443
+        if ($includeChildren) {
444
+            $subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
445
+            $this->eventLogger->end('fs:setup:user:path:find');
446
+
447
+            $needsFullSetup = array_reduce($subCachedMounts, function (bool $needsFullSetup, ICachedMountInfo $cachedMountInfo) {
448
+                return $needsFullSetup || $cachedMountInfo->getMountProvider() === '';
449
+            }, false);
450
+
451
+            if ($needsFullSetup) {
452
+                $this->logger->debug("mount has no provider set, performing full setup");
453
+                $this->setupForUser($user);
454
+                $this->eventLogger->end('fs:setup:user:path');
455
+                return;
456
+            } else {
457
+                foreach ($subCachedMounts as $cachedMount) {
458
+                    if (!in_array($cachedMount->getMountProvider(), $setupProviders)) {
459
+                        $currentProviders[] = $cachedMount->getMountProvider();
460
+                        $setupProviders[] = $cachedMount->getMountProvider();
461
+                        $mounts = array_merge($mounts, $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$cachedMount->getMountProvider()]));
462
+                    }
463
+                }
464
+            }
465
+        } else {
466
+            $this->eventLogger->end('fs:setup:user:path:find');
467
+        }
468
+
469
+        if (count($mounts)) {
470
+            $this->userMountCache->registerMounts($user, $mounts, $currentProviders);
471
+            $this->setupForUserWith($user, function () use ($mounts) {
472
+                array_walk($mounts, [$this->mountManager, 'addMount']);
473
+            });
474
+        } elseif (!$this->isSetupStarted($user)) {
475
+            $this->oneTimeUserSetup($user);
476
+        }
477
+        $this->eventLogger->end('fs:setup:user:path');
478
+    }
479
+
480
+    private function fullSetupRequired(IUser $user): bool {
481
+        // we perform a "cached" setup only after having done the full setup recently
482
+        // this is also used to trigger a full setup after handling events that are likely
483
+        // to change the available mounts
484
+        if (!isset($this->fullSetupRequired[$user->getUID()])) {
485
+            $this->fullSetupRequired[$user->getUID()] = !$this->cache->get($user->getUID());
486
+        }
487
+        return $this->fullSetupRequired[$user->getUID()];
488
+    }
489
+
490
+    /**
491
+     * @param string $path
492
+     * @param string[] $providers
493
+     */
494
+    public function setupForProvider(string $path, array $providers): void {
495
+        $user = $this->getUserForPath($path);
496
+        if (!$user) {
497
+            $this->setupRoot();
498
+            return;
499
+        }
500
+
501
+        if ($this->isSetupComplete($user)) {
502
+            return;
503
+        }
504
+
505
+        if ($this->fullSetupRequired($user)) {
506
+            $this->setupForUser($user);
507
+            return;
508
+        }
509
+
510
+        $this->eventLogger->start('fs:setup:user:providers', "Setup filesystem for " . implode(', ', $providers));
511
+
512
+        $this->oneTimeUserSetup($user);
513
+
514
+        // home providers are always used
515
+        $providers = array_filter($providers, function (string $provider) {
516
+            return !is_subclass_of($provider, IHomeMountProvider::class);
517
+        });
518
+
519
+        if (in_array('', $providers)) {
520
+            $this->setupForUser($user);
521
+            return;
522
+        }
523
+        $setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
524
+
525
+        $providers = array_diff($providers, $setupProviders);
526
+        if (count($providers) === 0) {
527
+            if (!$this->isSetupStarted($user)) {
528
+                $this->oneTimeUserSetup($user);
529
+            }
530
+            $this->eventLogger->end('fs:setup:user:providers');
531
+            return;
532
+        } else {
533
+            $this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
534
+            $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
535
+        }
536
+
537
+        $this->userMountCache->registerMounts($user, $mounts, $providers);
538
+        $this->setupForUserWith($user, function () use ($mounts) {
539
+            array_walk($mounts, [$this->mountManager, 'addMount']);
540
+        });
541
+        $this->eventLogger->end('fs:setup:user:providers');
542
+    }
543
+
544
+    public function tearDown() {
545
+        $this->setupUsers = [];
546
+        $this->setupUsersComplete = [];
547
+        $this->setupUserMountProviders = [];
548
+        $this->fullSetupRequired = [];
549
+        $this->rootSetup = false;
550
+        $this->mountManager->clear();
551
+        $this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
552
+    }
553
+
554
+    /**
555
+     * Get mounts from mount providers that are registered after setup
556
+     */
557
+    private function listenForNewMountProviders() {
558
+        if (!$this->listeningForProviders) {
559
+            $this->listeningForProviders = true;
560
+            $this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
561
+                IMountProvider $provider
562
+            ) {
563
+                foreach ($this->setupUsers as $userId) {
564
+                    $user = $this->userManager->get($userId);
565
+                    if ($user) {
566
+                        $mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
567
+                        array_walk($mounts, [$this->mountManager, 'addMount']);
568
+                    }
569
+                }
570
+            });
571
+        }
572
+    }
573
+
574
+    private function setupListeners() {
575
+        // note that this event handling is intentionally pessimistic
576
+        // clearing the cache to often is better than not enough
577
+
578
+        $this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
579
+            $this->cache->remove($event->getUser()->getUID());
580
+        });
581
+        $this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
582
+            $this->cache->remove($event->getUser()->getUID());
583
+        });
584
+        $this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
585
+            $this->cache->remove($event->getShare()->getSharedWith());
586
+        });
587
+        $this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event
588
+        ) {
589
+            if ($user = $event->getUser()) {
590
+                $this->cache->remove($user->getUID());
591
+            } else {
592
+                $this->cache->clear();
593
+            }
594
+        });
595
+
596
+        $genericEvents = [
597
+            'OCA\Circles\Events\CreatingCircleEvent',
598
+            'OCA\Circles\Events\DestroyingCircleEvent',
599
+            'OCA\Circles\Events\AddingCircleMemberEvent',
600
+            'OCA\Circles\Events\RemovingCircleMemberEvent',
601
+        ];
602
+
603
+        foreach ($genericEvents as $genericEvent) {
604
+            $this->eventDispatcher->addListener($genericEvent, function ($event) {
605
+                $this->cache->clear();
606
+            });
607
+        }
608
+    }
609 609
 }
Please login to merge, or discard this patch.