Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
19758a1
Revert "[stable33] Revert "Stable33 authoritative share""
artonge Mar 24, 2026
d572f82
fix: use proper index when deleting mounts
icewind1991 Mar 27, 2026
21b76ec
fix: default user_needs_share_refresh to true
icewind1991 Mar 27, 2026
0b3d846
test: add test for UserHomeSetupListener
icewind1991 Apr 2, 2026
9e258d7
fix: log when user is marked as needing share mount refresh
icewind1991 Apr 2, 2026
195fb19
fix: add optional user param to IUserMountCache::removeMount
icewind1991 Apr 9, 2026
f759d49
fix: don't trigger on-setup share update from inside the share listener
icewind1991 Apr 9, 2026
f19eeb4
feat: improve VerifyMountPointEvent event
icewind1991 Feb 9, 2026
7d3b4cb
fix: fix LazyUserFolder::getMountPoint
icewind1991 Apr 10, 2026
cdada60
fix: check share target parent in userfolders mount
icewind1991 Apr 10, 2026
e7e909c
fix: fix UserHomeSetupListener disabling with nested events
icewind1991 Apr 10, 2026
2fde14a
fix: don't trigger recursive SharesUpdatedListener when share is move…
icewind1991 Apr 10, 2026
89e9d79
perf: don't fetch child mounts when getting node parent
icewind1991 Apr 10, 2026
8e3ce8c
perf: only load a single mount at a time when checking for share conf…
icewind1991 Apr 14, 2026
3248ac3
perf: only load possible results in UserMountCache::getMountForPath
icewind1991 Apr 14, 2026
488866e
fix: only update share for the user who moved the share
icewind1991 Apr 17, 2026
0f83c43
fix: don't trigger recursive SharesUpdatedListener when share is move…
icewind1991 Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 56 additions & 26 deletions apps/files/lib/Command/Mount/ListMounts.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@

namespace OCA\Files\Command\Mount;

use OC\Core\Command\Base;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Config\IMountProviderCollection;
use OCP\Files\Config\IUserMountCache;
use OCP\Files\Mount\IMountPoint;
use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class ListMounts extends Command {
class ListMounts extends Base {
public function __construct(
private readonly IUserManager $userManager,
private readonly IUserMountCache $userMountCache,
Expand All @@ -28,52 +29,81 @@ public function __construct(
}

protected function configure(): void {
parent::configure();
$this
->setName('files:mount:list')
->setDescription('List of mounts for a user')
->addArgument('user', InputArgument::REQUIRED, 'User to list mounts for');
->addArgument('user', InputArgument::REQUIRED, 'User to list mounts for')
->addOption('cached-only', null, InputOption::VALUE_NONE, 'Only return cached mounts, prevents filesystem setup');
}

public function execute(InputInterface $input, OutputInterface $output): int {
$userId = $input->getArgument('user');
$cachedOnly = $input->getOption('cached-only');
$user = $this->userManager->get($userId);
if (!$user) {
$output->writeln("<error>User $userId not found</error>");
return 1;
}

$mounts = $this->mountProviderCollection->getMountsForUser($user);
$mounts[] = $this->mountProviderCollection->getHomeMountForUser($user);
/** @var array<string, IMountPoint> $cachedByMountpoint */
$mountsByMountpoint = array_combine(array_map(fn (IMountPoint $mount) => $mount->getMountPoint(), $mounts), $mounts);
if ($cachedOnly) {
$mounts = [];
} else {
$mounts = $this->mountProviderCollection->getMountsForUser($user);
$mounts[] = $this->mountProviderCollection->getHomeMountForUser($user);
}
/** @var array<string, IMountPoint> $cachedByMountPoint */
$mountsByMountPoint = array_combine(array_map(fn (IMountPoint $mount) => $mount->getMountPoint(), $mounts), $mounts);
usort($mounts, fn (IMountPoint $a, IMountPoint $b) => $a->getMountPoint() <=> $b->getMountPoint());

$cachedMounts = $this->userMountCache->getMountsForUser($user);
usort($cachedMounts, fn (ICachedMountInfo $a, ICachedMountInfo $b) => $a->getMountPoint() <=> $b->getMountPoint());
/** @var array<string, ICachedMountInfo> $cachedByMountpoint */
$cachedByMountpoint = array_combine(array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $cachedMounts), $cachedMounts);
$cachedByMountPoint = array_combine(array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $cachedMounts), $cachedMounts);

$format = $input->getOption('output');

foreach ($mounts as $mount) {
$output->writeln('<info>' . $mount->getMountPoint() . '</info>: ' . $mount->getStorageId());
if (isset($cachedByMountpoint[$mount->getMountPoint()])) {
$cached = $cachedByMountpoint[$mount->getMountPoint()];
$output->writeln("\t- provider: " . $cached->getMountProvider());
$output->writeln("\t- storage id: " . $cached->getStorageId());
$output->writeln("\t- root id: " . $cached->getRootId());
} else {
$output->writeln("\t<error>not registered</error>");
if ($format === self::OUTPUT_FORMAT_PLAIN) {
foreach ($mounts as $mount) {
$output->writeln('<info>' . $mount->getMountPoint() . '</info>: ' . $mount->getStorageId());
if (isset($cachedByMountPoint[$mount->getMountPoint()])) {
$cached = $cachedByMountPoint[$mount->getMountPoint()];
$output->writeln("\t- provider: " . $cached->getMountProvider());
$output->writeln("\t- storage id: " . $cached->getStorageId());
$output->writeln("\t- root id: " . $cached->getRootId());
} else {
$output->writeln("\t<error>not registered</error>");
}
}
}
foreach ($cachedMounts as $cachedMount) {
if (!isset($mountsByMountpoint[$cachedMount->getMountPoint()])) {
$output->writeln('<info>' . $cachedMount->getMountPoint() . '</info>:');
$output->writeln("\t<error>registered but no longer provided</error>");
$output->writeln("\t- provider: " . $cachedMount->getMountProvider());
$output->writeln("\t- storage id: " . $cachedMount->getStorageId());
$output->writeln("\t- root id: " . $cachedMount->getRootId());
foreach ($cachedMounts as $cachedMount) {
if ($cachedOnly || !isset($mountsByMountPoint[$cachedMount->getMountPoint()])) {
$output->writeln('<info>' . $cachedMount->getMountPoint() . '</info>:');
if (!$cachedOnly) {
$output->writeln("\t<error>registered but no longer provided</error>");
}
$output->writeln("\t- provider: " . $cachedMount->getMountProvider());
$output->writeln("\t- storage id: " . $cachedMount->getStorageId());
$output->writeln("\t- root id: " . $cachedMount->getRootId());
}
}
} else {
$cached = array_map(fn (ICachedMountInfo $cachedMountInfo) => [
'mountpoint' => $cachedMountInfo->getMountPoint(),
'provider' => $cachedMountInfo->getMountProvider(),
'storage_id' => $cachedMountInfo->getStorageId(),
'root_id' => $cachedMountInfo->getRootId(),
], $cachedMounts);
$provided = array_map(fn (IMountPoint $cachedMountInfo) => [
'mountpoint' => $cachedMountInfo->getMountPoint(),
'provider' => $cachedMountInfo->getMountProvider(),
'storage_id' => $cachedMountInfo->getStorageId(),
'root_id' => $cachedMountInfo->getStorageRootId(),
], $mounts);
$this->writeArrayInOutputFormat($input, $output, array_filter([
'cached' => $cached,
'provided' => $cachedOnly ? null : $provided,
]));
}

return 0;
}

Expand Down
20 changes: 18 additions & 2 deletions apps/files/lib/Service/OwnershipTransferService.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use OCA\Files_External\Config\ConfigAdapter;
use OCA\GroupFolders\Mount\GroupMountPoint;
use OCP\Encryption\IManager as IEncryptionManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Config\IHomeMountProvider;
use OCP\Files\Config\IUserMountCache;
use OCP\Files\File;
Expand All @@ -31,6 +32,7 @@
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCP\Server;
use OCP\Share\Events\ShareTransferredEvent;
use OCP\Share\IManager as IShareManager;
use OCP\Share\IShare;
use Symfony\Component\Console\Helper\ProgressBar;
Expand All @@ -53,6 +55,7 @@ public function __construct(
private IUserManager $userManager,
private IFactory $l10nFactory,
private IRootFolder $rootFolder,
private IEventDispatcher $eventDispatcher,
) {
}

Expand Down Expand Up @@ -567,20 +570,23 @@ private function restoreShares(
} catch (\Throwable $e) {
$output->writeln('<error>Could not restore share with id ' . $share->getId() . ':' . $e->getMessage() . ' : ' . $e->getTraceAsString() . '</error>');
}
$this->eventDispatcher->dispatchTyped(new ShareTransferredEvent($share));
$progress->advance();
}
$progress->finish();
$output->writeln('');
}

private function transferIncomingShares(string $sourceUid,
private function transferIncomingShares(
string $sourceUid,
string $destinationUid,
array $sourceShares,
array $destinationShares,
OutputInterface $output,
string $path,
string $finalTarget,
bool $move): void {
bool $move,
): void {
$output->writeln('Restoring incoming shares ...');
$progress = new ProgressBar($output, count($sourceShares));
$prefix = "$destinationUid/files";
Expand Down Expand Up @@ -619,8 +625,11 @@ private function transferIncomingShares(string $sourceUid,
if ($move) {
continue;
}
$oldMountPoint = $this->getShareMountPoint($destinationUid, $share->getTarget());
$newMountPoint = $this->getShareMountPoint($destinationUid, $shareTarget);
$share->setTarget($shareTarget);
$this->shareManager->moveShare($share, $destinationUid);
$this->mountManager->moveMount($oldMountPoint, $newMountPoint);
continue;
}
$this->shareManager->deleteShare($share);
Expand All @@ -638,8 +647,11 @@ private function transferIncomingShares(string $sourceUid,
if ($move) {
continue;
}
$oldMountPoint = $this->getShareMountPoint($destinationUid, $share->getTarget());
$newMountPoint = $this->getShareMountPoint($destinationUid, $shareTarget);
$share->setTarget($shareTarget);
$this->shareManager->moveShare($share, $destinationUid);
$this->mountManager->moveMount($oldMountPoint, $newMountPoint);
continue;
}
} catch (NotFoundException $e) {
Expand All @@ -652,4 +664,8 @@ private function transferIncomingShares(string $sourceUid,
$progress->finish();
$output->writeln('');
}

private function getShareMountPoint(string $uid, string $target): string {
return '/' . $uid . '/files/' . trim($target, '/') . '/';
}
}
8 changes: 4 additions & 4 deletions apps/files_external/lib/Service/MountCacheService.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function handle(Event $event): void {

public function handleDeletedStorage(StorageConfig $storage): void {
foreach ($this->applicableHelper->getUsersForStorage($storage) as $user) {
$this->userMountCache->removeMount($storage->getMountPointForUser($user));
$this->userMountCache->removeMount($storage->getMountPointForUser($user), $user);
}
}

Expand All @@ -87,7 +87,7 @@ public function handleAddedStorage(StorageConfig $storage): void {

public function handleUpdatedStorage(StorageConfig $oldStorage, StorageConfig $newStorage): void {
foreach ($this->applicableHelper->diffApplicable($oldStorage, $newStorage) as $user) {
$this->userMountCache->removeMount($oldStorage->getMountPointForUser($user));
$this->userMountCache->removeMount($oldStorage->getMountPointForUser($user), $user);
}
foreach ($this->applicableHelper->diffApplicable($newStorage, $oldStorage) as $user) {
$this->registerForUser($user, $newStorage);
Expand Down Expand Up @@ -156,7 +156,7 @@ private function handleUserRemoved(IGroup $group, IUser $user): void {
$storages = $this->storagesService->getAllStoragesForGroup($group);
foreach ($storages as $storage) {
if (!$this->applicableHelper->isApplicableForUser($storage, $user)) {
$this->userMountCache->removeMount($storage->getMountPointForUser($user));
$this->userMountCache->removeMount($storage->getMountPointForUser($user), $user);
}
}
}
Expand All @@ -181,7 +181,7 @@ private function handleGroupDeleted(IGroup $group): void {
private function removeGroupFromStorage(StorageConfig $storage, IGroup $group): void {
foreach ($group->searchUsers('') as $user) {
if (!$this->applicableHelper->isApplicableForUser($storage, $user)) {
$this->userMountCache->removeMount($storage->getMountPointForUser($user));
$this->userMountCache->removeMount($storage->getMountPointForUser($user), $user);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions apps/files_sharing/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => $baseDir . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => $baseDir . '/../lib/Listener/ShareInteractionListener.php',
'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => $baseDir . '/../lib/Listener/SharesUpdatedListener.php',
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => $baseDir . '/../lib/Listener/UserAddedToGroupListener.php',
'OCA\\Files_Sharing\\Listener\\UserHomeSetupListener' => $baseDir . '/../lib/Listener/UserHomeSetupListener.php',
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => $baseDir . '/../lib/Listener/UserShareAcceptanceListener.php',
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => $baseDir . '/../lib/Middleware/OCSShareAPIMiddleware.php',
'OCA\\Files_Sharing\\Middleware\\ShareInfoMiddleware' => $baseDir . '/../lib/Middleware/ShareInfoMiddleware.php',
Expand All @@ -97,6 +99,7 @@
'OCA\\Files_Sharing\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',
'OCA\\Files_Sharing\\ShareBackend\\File' => $baseDir . '/../lib/ShareBackend/File.php',
'OCA\\Files_Sharing\\ShareBackend\\Folder' => $baseDir . '/../lib/ShareBackend/Folder.php',
'OCA\\Files_Sharing\\ShareRecipientUpdater' => $baseDir . '/../lib/ShareRecipientUpdater.php',
'OCA\\Files_Sharing\\ShareTargetValidator' => $baseDir . '/../lib/ShareTargetValidator.php',
'OCA\\Files_Sharing\\SharedMount' => $baseDir . '/../lib/SharedMount.php',
'OCA\\Files_Sharing\\SharedStorage' => $baseDir . '/../lib/SharedStorage.php',
Expand Down
3 changes: 3 additions & 0 deletions apps/files_sharing/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => __DIR__ . '/..' . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/ShareInteractionListener.php',
'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => __DIR__ . '/..' . '/../lib/Listener/SharesUpdatedListener.php',
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupListener.php',
'OCA\\Files_Sharing\\Listener\\UserHomeSetupListener' => __DIR__ . '/..' . '/../lib/Listener/UserHomeSetupListener.php',
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => __DIR__ . '/..' . '/../lib/Listener/UserShareAcceptanceListener.php',
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/OCSShareAPIMiddleware.php',
'OCA\\Files_Sharing\\Middleware\\ShareInfoMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/ShareInfoMiddleware.php',
Expand All @@ -112,6 +114,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
'OCA\\Files_Sharing\\ShareBackend\\File' => __DIR__ . '/..' . '/../lib/ShareBackend/File.php',
'OCA\\Files_Sharing\\ShareBackend\\Folder' => __DIR__ . '/..' . '/../lib/ShareBackend/Folder.php',
'OCA\\Files_Sharing\\ShareRecipientUpdater' => __DIR__ . '/..' . '/../lib/ShareRecipientUpdater.php',
'OCA\\Files_Sharing\\ShareTargetValidator' => __DIR__ . '/..' . '/../lib/ShareTargetValidator.php',
'OCA\\Files_Sharing\\SharedMount' => __DIR__ . '/..' . '/../lib/SharedMount.php',
'OCA\\Files_Sharing\\SharedStorage' => __DIR__ . '/..' . '/../lib/SharedStorage.php',
Expand Down
21 changes: 21 additions & 0 deletions apps/files_sharing/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use OCA\Files\Event\LoadSidebar;
use OCA\Files_Sharing\Capabilities;
use OCA\Files_Sharing\Config\ConfigLexicon;
use OCA\Files_Sharing\Event\UserShareAccessUpdatedEvent;
use OCA\Files_Sharing\External\Manager;
use OCA\Files_Sharing\External\MountProvider as ExternalMountProvider;
use OCA\Files_Sharing\Helper;
Expand All @@ -24,7 +25,9 @@
use OCA\Files_Sharing\Listener\LoadPublicFileRequestAuthListener;
use OCA\Files_Sharing\Listener\LoadSidebarListener;
use OCA\Files_Sharing\Listener\ShareInteractionListener;
use OCA\Files_Sharing\Listener\SharesUpdatedListener;
use OCA\Files_Sharing\Listener\UserAddedToGroupListener;
use OCA\Files_Sharing\Listener\UserHomeSetupListener;
use OCA\Files_Sharing\Listener\UserShareAcceptanceListener;
use OCA\Files_Sharing\Middleware\OCSShareAPIMiddleware;
use OCA\Files_Sharing\Middleware\ShareInfoMiddleware;
Expand All @@ -46,13 +49,19 @@
use OCP\Files\Events\BeforeDirectFileDownloadEvent;
use OCP\Files\Events\BeforeZipCreatedEvent;
use OCP\Files\Events\Node\BeforeNodeReadEvent;
use OCP\Files\Events\UserHomeSetupEvent;
use OCP\Group\Events\BeforeGroupDeletedEvent;
use OCP\Group\Events\GroupChangedEvent;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\Group\Events\UserAddedEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\IConfig;
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;
use OCP\Util;
Expand Down Expand Up @@ -111,6 +120,18 @@ function () use ($c) {
// File request auth
$context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadPublicFileRequestAuthListener::class);

// Update mounts
$context->registerEventListener(ShareCreatedEvent::class, SharesUpdatedListener::class);
$context->registerEventListener(BeforeShareDeletedEvent::class, SharesUpdatedListener::class);
$context->registerEventListener(ShareTransferredEvent::class, SharesUpdatedListener::class);
$context->registerEventListener(UserAddedEvent::class, SharesUpdatedListener::class);
$context->registerEventListener(UserRemovedEvent::class, SharesUpdatedListener::class);
$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);
}

Expand Down
8 changes: 7 additions & 1 deletion apps/files_sharing/lib/Config/ConfigLexicon.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class ConfigLexicon implements ILexicon {
public const SHOW_FEDERATED_AS_INTERNAL = 'show_federated_shares_as_internal';
public const SHOW_FEDERATED_TO_TRUSTED_AS_INTERNAL = 'show_federated_shares_to_trusted_servers_as_internal';
public const EXCLUDE_RESHARE_FROM_EDIT = 'shareapi_exclude_reshare_from_edit';
public const UPDATE_CUTOFF_TIME = 'update_cutoff_time';
public const USER_NEEDS_SHARE_REFRESH = 'user_needs_share_refresh';

public function getStrictness(): Strictness {
return Strictness::IGNORE;
Expand All @@ -34,10 +36,14 @@ public function getAppConfigs(): array {
new Entry(self::SHOW_FEDERATED_AS_INTERNAL, ValueType::BOOL, false, 'shows federated shares as internal shares', true),
new Entry(self::SHOW_FEDERATED_TO_TRUSTED_AS_INTERNAL, ValueType::BOOL, false, 'shows federated shares to trusted servers as internal shares', true),
new Entry(self::EXCLUDE_RESHARE_FROM_EDIT, ValueType::BOOL, false, 'Exclude reshare permission from "Allow editing" bundled permissions'),

new Entry(self::UPDATE_CUTOFF_TIME, ValueType::FLOAT, 3.0, 'For how how long do we update the share data immediately before switching to only marking the user'),
];
}

public function getUserConfigs(): array {
return [];
return [
new Entry(self::USER_NEEDS_SHARE_REFRESH, ValueType::BOOL, true, 'whether a user needs to have the receiving share data refreshed for possible changes'),
];
}
}
Loading
Loading