From e7fcb6c7f503d1593f4e378c3a9e0bc44670db37 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 12 Mar 2026 19:15:39 +0100 Subject: [PATCH] fix: handle share moves Signed-off-by: Robin Appelman --- .../files_sharing/lib/AppInfo/Application.php | 2 ++ .../lib/Listener/SharesUpdatedListener.php | 11 ++++++- .../lib/ShareRecipientUpdater.php | 25 +++++++++++--- .../sharing_features/sharing-v1-part4.feature | 12 +++++++ lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/Share20/Manager.php | 7 +++- lib/private/Share20/Share.php | 13 ++++++++ lib/public/Share/Events/ShareMovedEvent.php | 33 +++++++++++++++++++ lib/public/Share/IShare.php | 7 ++++ 10 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 lib/public/Share/Events/ShareMovedEvent.php diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php index faef075d4e7..8f5c4c4c461 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -57,6 +57,7 @@ use OCP\IDBConnection; use OCP\IGroup; use OCP\Share\Events\BeforeShareDeletedEvent; use OCP\Share\Events\ShareCreatedEvent; +use OCP\Share\Events\ShareMovedEvent; use OCP\Share\Events\ShareTransferredEvent; use OCP\User\Events\UserChangedEvent; use OCP\User\Events\UserDeletedEvent; @@ -125,6 +126,7 @@ class Application extends App implements IBootstrap { $context->registerEventListener(BeforeGroupDeletedEvent::class, SharesUpdatedListener::class); $context->registerEventListener(GroupDeletedEvent::class, SharesUpdatedListener::class); $context->registerEventListener(UserShareAccessUpdatedEvent::class, SharesUpdatedListener::class); + $context->registerEventListener(ShareMovedEvent::class, SharesUpdatedListener::class); $context->registerEventListener(UserHomeSetupEvent::class, UserHomeSetupListener::class); $context->registerConfigLexicon(ConfigLexicon::class); diff --git a/apps/files_sharing/lib/Listener/SharesUpdatedListener.php b/apps/files_sharing/lib/Listener/SharesUpdatedListener.php index 163a1aee229..92a1163df8f 100644 --- a/apps/files_sharing/lib/Listener/SharesUpdatedListener.php +++ b/apps/files_sharing/lib/Listener/SharesUpdatedListener.php @@ -23,6 +23,7 @@ use OCP\IAppConfig; use OCP\IUser; use OCP\Share\Events\BeforeShareDeletedEvent; use OCP\Share\Events\ShareCreatedEvent; +use OCP\Share\Events\ShareMovedEvent; use OCP\Share\Events\ShareTransferredEvent; use OCP\Share\IManager; use Psr\Clock\ClockInterface; @@ -31,7 +32,7 @@ use Psr\Clock\ClockInterface; * Listen to various events that can change what shares a user has access to * * @psalm-type GroupEvents = UserAddedEvent|UserRemovedEvent|GroupDeletedEvent|BeforeGroupDeletedEvent - * @template-implements IEventListener + * @template-implements IEventListener */ class SharesUpdatedListener implements IEventListener { /** @@ -88,6 +89,14 @@ class SharesUpdatedListener implements IEventListener { } } } + if ($event instanceof ShareMovedEvent) { + $share = $event->getShare(); + foreach ($this->shareManager->getUsersForShare($share) as $user) { + $this->markOrRun($user, function () use ($user, $share) { + $this->shareUpdater->updateForMovedShare($user, $share); + }); + } + } if ($event instanceof BeforeShareDeletedEvent) { $share = $event->getShare(); foreach ($this->shareManager->getUsersForShare($share) as $user) { diff --git a/apps/files_sharing/lib/ShareRecipientUpdater.php b/apps/files_sharing/lib/ShareRecipientUpdater.php index 83cf681344c..62033b7dd0a 100644 --- a/apps/files_sharing/lib/ShareRecipientUpdater.php +++ b/apps/files_sharing/lib/ShareRecipientUpdater.php @@ -47,7 +47,7 @@ class ShareRecipientUpdater { $mountsChanged = count($shares) !== count($shareMounts); foreach ($shares as $share) { [$parentShare, $groupedShares] = $share; - $mountPoint = '/' . $user->getUID() . '/files/' . trim($parentShare->getTarget(), '/') . '/'; + $mountPoint = $this->getMountPointFromTarget($user, $parentShare->getTarget()); $mountKey = $parentShare->getNodeId() . '::' . $mountPoint; if (!isset($cachedMounts[$mountKey])) { $mountsChanged = true; @@ -72,17 +72,34 @@ class ShareRecipientUpdater { $mountsByPath = array_combine($mountPoints, $cachedMounts); $target = $this->shareTargetValidator->verifyMountPoint($user, $share, $mountsByPath, [$share]); - $mountPoint = '/' . $user->getUID() . '/files/' . trim($target, '/') . '/'; + $mountPoint = $this->getMountPointFromTarget($user, $target); $this->userMountCache->addMount($user, $mountPoint, $share->getNode()->getData(), MountProvider::class); } + private function getMountPointFromTarget(IUser $user, string $target): string { + return '/' . $user->getUID() . '/files/' . trim($target, '/') . '/'; + } + /** * Process a single deleted share for a user */ public function updateForDeletedShare(IUser $user, IShare $share): void { - $mountPoint = '/' . $user->getUID() . '/files/' . trim($share->getTarget(), '/') . '/'; + $this->userMountCache->removeMount($this->getMountPointFromTarget($user, $share->getTarget())); + } - $this->userMountCache->removeMount($mountPoint); + /** + * Process a single moved share for a user + */ + public function updateForMovedShare(IUser $user, IShare $share): void { + $originalTarget = $share->getOriginalTarget(); + if ($originalTarget != null) { + $newMountPoint = $this->getMountPointFromTarget($user, $share->getTarget()); + $oldMountPoint = $this->getMountPointFromTarget($user, $originalTarget); + $this->userMountCache->removeMount($oldMountPoint); + $this->userMountCache->addMount($user, $newMountPoint, $share->getNode()->getData(), MountProvider::class); + } else { + $this->updateForUser($user); + } } } diff --git a/build/integration/sharing_features/sharing-v1-part4.feature b/build/integration/sharing_features/sharing-v1-part4.feature index 746ac93d6a3..5e710d7f286 100644 --- a/build/integration/sharing_features/sharing-v1-part4.feature +++ b/build/integration/sharing_features/sharing-v1-part4.feature @@ -414,3 +414,15 @@ Scenario: User added/removed to group share with marking | /user0/files/textfile0 (2).txt/ | When Connecting to dav endpoint Then Share mounts for "user0" are empty + + Scenario: Share moved without marking + Given As an "admin" + And user "user0" exists + And user "user1" exists + And file "textfile0.txt" of user "user1" is shared with user "user0" + And As an "user0" + Then Share mounts for "user0" match + | /user0/files/textfile0 (2).txt/ | + When User "user0" moves file "/textfile0 (2).txt" to "/target.txt" + Then Share mounts for "user0" match + | /user0/files/target.txt/ | diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 736624f93c7..ca55f66b3cf 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -850,6 +850,7 @@ return array( 'OCP\\Share\\Events\\ShareCreatedEvent' => $baseDir . '/lib/public/Share/Events/ShareCreatedEvent.php', 'OCP\\Share\\Events\\ShareDeletedEvent' => $baseDir . '/lib/public/Share/Events/ShareDeletedEvent.php', 'OCP\\Share\\Events\\ShareDeletedFromSelfEvent' => $baseDir . '/lib/public/Share/Events/ShareDeletedFromSelfEvent.php', + 'OCP\\Share\\Events\\ShareMovedEvent' => $baseDir . '/lib/public/Share/Events/ShareMovedEvent.php', 'OCP\\Share\\Events\\ShareTransferredEvent' => $baseDir . '/lib/public/Share/Events/ShareTransferredEvent.php', 'OCP\\Share\\Events\\VerifyMountPointEvent' => $baseDir . '/lib/public/Share/Events/VerifyMountPointEvent.php', 'OCP\\Share\\Exceptions\\AlreadySharedException' => $baseDir . '/lib/public/Share/Exceptions/AlreadySharedException.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 4caffadca61..0b4375c0a7f 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -891,6 +891,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Share\\Events\\ShareCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/ShareCreatedEvent.php', 'OCP\\Share\\Events\\ShareDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/ShareDeletedEvent.php', 'OCP\\Share\\Events\\ShareDeletedFromSelfEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/ShareDeletedFromSelfEvent.php', + 'OCP\\Share\\Events\\ShareMovedEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/ShareMovedEvent.php', 'OCP\\Share\\Events\\ShareTransferredEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/ShareTransferredEvent.php', 'OCP\\Share\\Events\\VerifyMountPointEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/VerifyMountPointEvent.php', 'OCP\\Share\\Exceptions\\AlreadySharedException' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/AlreadySharedException.php', diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 9515a578da9..27fb205d5f8 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -51,6 +51,7 @@ use OCP\Share\Events\ShareAcceptedEvent; use OCP\Share\Events\ShareCreatedEvent; use OCP\Share\Events\ShareDeletedEvent; use OCP\Share\Events\ShareDeletedFromSelfEvent; +use OCP\Share\Events\ShareMovedEvent; use OCP\Share\Exceptions\AlreadySharedException; use OCP\Share\Exceptions\GenericShareException; use OCP\Share\Exceptions\ShareNotFound; @@ -1200,7 +1201,11 @@ class Manager implements IManager { [$providerId,] = $this->splitFullId($share->getFullId()); $provider = $this->factory->getProvider($providerId); - return $provider->move($share, $recipientId); + $result = $provider->move($share, $recipientId); + + $this->dispatchEvent(new ShareMovedEvent($share), 'share moved'); + + return $result; } #[Override] diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index 3c395caec4b..3dd57fb3565 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -62,6 +62,8 @@ class Share implements IShare { private ?int $parent = null; /** @var string */ private $target; + /** @var string */ + private ?string $originalTarget = null; /** @var \DateTime */ private $shareTime; /** @var bool */ @@ -514,10 +516,21 @@ class Share implements IShare { * @inheritdoc */ public function setTarget($target) { + // if the target is changed, save the original target + if ($this->target && !$this->originalTarget) { + $this->originalTarget = $this->target; + } $this->target = $target; return $this; } + /** + * Return the original target, if this share was moved + */ + public function getOriginalTarget(): ?string { + return $this->originalTarget; + } + /** * @inheritdoc */ diff --git a/lib/public/Share/Events/ShareMovedEvent.php b/lib/public/Share/Events/ShareMovedEvent.php new file mode 100644 index 00000000000..36c020fc0c4 --- /dev/null +++ b/lib/public/Share/Events/ShareMovedEvent.php @@ -0,0 +1,33 @@ +share; + } +} diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php index da8b9e4868a..7bd1b8c5ebd 100644 --- a/lib/public/Share/IShare.php +++ b/lib/public/Share/IShare.php @@ -545,6 +545,13 @@ interface IShare { */ public function setTarget($target); + /** + * Return the original target, if this share was moved + * + * @since 33.0.0 + */ + public function getOriginalTarget(): ?string; + /** * Get the target path of this share relative to the recipients user folder. *