Skip to content

Commit 73dd45b

Browse files
authored
Merge pull request #57289 from nextcloud/feature/54562/drop-mounts-on-full-or-provider-setup
Feature/54562/drop mounts on full or provider setup
2 parents 5403284 + 2d22c4f commit 73dd45b

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

lib/private/Files/SetupManager.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
use OCP\Files\Config\IUserMountCache;
4141
use OCP\Files\Events\BeforeFileSystemSetupEvent;
4242
use OCP\Files\Events\InvalidateMountCacheEvent;
43+
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
4344
use OCP\Files\Events\Node\FilesystemTornDownEvent;
4445
use OCP\Files\Mount\IMountManager;
4546
use OCP\Files\Mount\IMountPoint;
@@ -236,6 +237,8 @@ public function setupForUser(IUser $user): void {
236237

237238
$this->eventLogger->start('fs:setup:user:full', 'Setup full filesystem for user');
238239

240+
$this->dropPartialMountsForUser($user);
241+
239242
$this->setupUserMountProviders[$user->getUID()] ??= [];
240243
$previouslySetupProviders = $this->setupUserMountProviders[$user->getUID()];
241244

@@ -658,6 +661,7 @@ public function setupForProvider(string $path, array $providers): void {
658661
$this->eventLogger->end('fs:setup:user:providers');
659662
return;
660663
} else {
664+
$this->dropPartialMountsForUser($user, $providers);
661665
$this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
662666
$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
663667
}
@@ -713,6 +717,16 @@ private function setupListeners() {
713717
$this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
714718
$this->cache->remove($event->getShare()->getSharedWith());
715719
});
720+
$this->eventDispatcher->addListener(BeforeNodeRenamedEvent::class, function (BeforeNodeRenamedEvent $event) {
721+
// update cache information that is cached by mount point
722+
$from = rtrim($event->getSource()->getPath(), '/') . '/';
723+
$to = rtrim($event->getTarget()->getPath(), '/') . '/';
724+
$existingMount = $this->setupMountProviderPaths[$from] ?? null;
725+
if ($existingMount !== null) {
726+
$this->setupMountProviderPaths[$to] = $this->setupMountProviderPaths[$from];
727+
unset($this->setupMountProviderPaths[$from]);
728+
}
729+
});
716730
$this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
717731
) {
718732
if ($user = $event->getUser()) {
@@ -741,4 +755,39 @@ private function registerMounts(IUser $user, array $mounts, ?array $mountProvide
741755
$this->userMountCache->registerMounts($user, $mounts, $mountProviderClasses);
742756
}
743757
}
758+
759+
/**
760+
* Drops partially set-up mounts for the given user
761+
* @param class-string<IMountProvider>[] $providers
762+
*/
763+
public function dropPartialMountsForUser(IUser $user, array $providers = []): void {
764+
// mounts are cached by mount-point
765+
$mounts = $this->mountManager->getAll();
766+
$partialMounts = array_filter($this->setupMountProviderPaths,
767+
static function (string $mountPoint) use (
768+
$providers,
769+
$user,
770+
$mounts
771+
) {
772+
$isUserMount = str_starts_with($mountPoint, '/' . $user->getUID() . '/files');
773+
774+
if (!$isUserMount) {
775+
return false;
776+
}
777+
778+
$mountProvider = ($mounts[$mountPoint] ?? null)?->getMountProvider();
779+
780+
return empty($providers)
781+
|| \in_array($mountProvider, $providers, true);
782+
},
783+
ARRAY_FILTER_USE_KEY);
784+
785+
if (!empty($partialMounts)) {
786+
// remove partially set up mounts
787+
foreach ($partialMounts as $mountPoint => $_mount) {
788+
$this->mountManager->removeMount($mountPoint);
789+
unset($this->setupMountProviderPaths[$mountPoint]);
790+
}
791+
}
792+
}
744793
}

tests/lib/Files/SetupManagerTest.php

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,143 @@ public function testSetupForPathHandlesPartialAndFullProvidersWithChildren(): vo
495495
$this->setupManager->setupForPath($this->path, true);
496496
}
497497

498+
public function testSetupForUserResetsUserPaths(): void {
499+
$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
500+
501+
$this->userMountCache->expects($this->once())
502+
->method('getMountForPath')
503+
->with($this->user, $this->path)
504+
->willReturn($cachedMount);
505+
$this->userMountCache->expects($this->never())
506+
->method('getMountsInPath');
507+
508+
$this->fileAccess->expects($this->once())
509+
->method('getByFileId')
510+
->with(42)
511+
->willReturn($this->createMock(CacheEntry::class));
512+
513+
$partialMount = $this->createMock(IMountPoint::class);
514+
515+
$this->mountProviderCollection->expects($this->once())
516+
->method('getUserMountsFromProviderByPath')
517+
->with(
518+
SetupManagerTestPartialMountProvider::class,
519+
$this->path,
520+
false,
521+
$this->callback(function (array $args) use ($cachedMount) {
522+
$this->assertCount(1, $args);
523+
$this->assertInstanceOf(IMountProviderArgs::class,
524+
$args[0]);
525+
$this->assertSame($cachedMount, $args[0]->mountInfo);
526+
return true;
527+
})
528+
)
529+
->willReturn([$partialMount]);
530+
531+
$homeMount = $this->createMock(IMountPoint::class);
532+
533+
$this->mountProviderCollection->expects($this->once())
534+
->method('getHomeMountForUser')
535+
->willReturn($homeMount);
536+
$this->mountProviderCollection->expects($this->never())
537+
->method('getUserMountsForProviderClasses');
538+
539+
$invokedCount = $this->exactly(2);
540+
$addMountExpectations = [
541+
1 => $homeMount,
542+
2 => $partialMount,
543+
];
544+
$this->mountManager->expects($invokedCount)
545+
->method('addMount')
546+
->willReturnCallback($this->getAddMountCheckCallback($invokedCount,
547+
$addMountExpectations));
548+
549+
550+
// setting up for $path but then for user should remove the setup path
551+
$this->setupManager->setupForPath($this->path, false);
552+
553+
// note that only the mount known by SetupManrger is removed not the
554+
// home mount, because MountManager is mocked
555+
$this->mountManager->expects($this->once())
556+
->method('removeMount')
557+
->with($this->mountPoint);
558+
559+
$this->setupManager->setupForUser($this->user);
560+
}
561+
562+
/**
563+
* Tests that after a path is setup by a
564+
*/
565+
public function testSetupForProviderResetsUserProviderPaths(): void {
566+
$cachedMount = $this->getCachedMountInfo($this->mountPoint, 42);
567+
568+
$this->userMountCache->expects($this->once())
569+
->method('getMountForPath')
570+
->with($this->user, $this->path)
571+
->willReturn($cachedMount);
572+
$this->userMountCache->expects($this->never())
573+
->method('getMountsInPath');
574+
575+
$this->fileAccess->expects($this->once())
576+
->method('getByFileId')
577+
->with(42)
578+
->willReturn($this->createMock(CacheEntry::class));
579+
580+
$partialMount = $this->createMock(IMountPoint::class);
581+
$partialMount->expects($this->once())->method('getMountProvider')
582+
->willReturn(SetupManagerTestFullMountProvider::class);
583+
584+
$this->mountProviderCollection->expects($this->once())
585+
->method('getUserMountsFromProviderByPath')
586+
->with(
587+
SetupManagerTestPartialMountProvider::class,
588+
$this->path,
589+
false,
590+
$this->callback(function (array $args) use ($cachedMount) {
591+
$this->assertCount(1, $args);
592+
$this->assertInstanceOf(IMountProviderArgs::class,
593+
$args[0]);
594+
$this->assertSame($cachedMount, $args[0]->mountInfo);
595+
return true;
596+
})
597+
)
598+
->willReturn([$partialMount]);
599+
600+
$homeMount = $this->createMock(IMountPoint::class);
601+
602+
$this->mountProviderCollection->expects($this->once())
603+
->method('getHomeMountForUser')
604+
->willReturn($homeMount);
605+
606+
$invokedCount = $this->exactly(2);
607+
$addMountExpectations = [
608+
1 => $homeMount,
609+
2 => $partialMount,
610+
];
611+
$this->mountManager->expects($invokedCount)
612+
->method('addMount')
613+
->willReturnCallback($this->getAddMountCheckCallback($invokedCount,
614+
$addMountExpectations));
615+
$this->mountManager->expects($this->once())->method('getAll')
616+
->willReturn([$this->mountPoint => $partialMount]);
617+
618+
// setting up for $path but then for user should remove the setup path
619+
$this->setupManager->setupForPath($this->path, false);
620+
621+
// note that only the mount known by SetupManrger is removed not the
622+
// home mount, because MountManager is mocked
623+
$this->mountManager->expects($this->once())
624+
->method('removeMount')
625+
->with($this->mountPoint);
626+
627+
$this->mountProviderCollection->expects($this->once())
628+
->method('getUserMountsForProviderClasses')
629+
->with($this->user, [SetupManagerTestFullMountProvider::class]);
630+
631+
$this->setupManager->setupForProvider($this->path,
632+
[SetupManagerTestFullMountProvider::class]);
633+
}
634+
498635
private function getAddMountCheckCallback(InvokedCount $invokedCount, $expectations): \Closure {
499636
return function (IMountPoint $actualMount) use ($invokedCount, $expectations) {
500637
$expectedMount = $expectations[$invokedCount->numberOfInvocations()] ?? null;

0 commit comments

Comments
 (0)