fix: handle share moves

Signed-off-by: Robin Appelman <robin@icewind.nl>
This commit is contained in:
Robin Appelman 2026-03-12 19:15:39 +01:00
parent adb8ed914f
commit e7fcb6c7f5
No known key found for this signature in database
GPG key ID: 42B69D8A64526EFB
10 changed files with 106 additions and 6 deletions

View file

@ -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);

View file

@ -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<GroupEvents|ShareCreatedEvent|ShareTransferredEvent|BeforeShareDeletedEvent|UserShareAccessUpdatedEvent>
* @template-implements IEventListener<GroupEvents|ShareCreatedEvent|ShareTransferredEvent|BeforeShareDeletedEvent|UserShareAccessUpdatedEvent|ShareMovedEvent>
*/
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) {

View file

@ -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);
}
}
}

View file

@ -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/ |

View file

@ -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',

View file

@ -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',

View file

@ -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]

View file

@ -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
*/

View file

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCP\Share\Events;
use OCP\EventDispatcher\Event;
use OCP\Share\IShare;
/**
* @since 33.0.0
*/
class ShareMovedEvent extends Event {
/**
* @since 33.0.0
*/
public function __construct(
private readonly IShare $share,
) {
parent::__construct();
}
/**
* @since 33.0.0
*/
public function getShare(): IShare {
return $this->share;
}
}

View file

@ -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.
*