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