Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0c6349a
fix: only validate mounts for new share
icewind1991 Jan 23, 2026
ddeea55
feat: add event for user home mount having being setup
icewind1991 Feb 12, 2026
c4ed8a5
chore: move share recipient validation logic to a separate class
icewind1991 Feb 12, 2026
ebb8527
feat: postpone receiving share validation after processing a certain …
icewind1991 Feb 12, 2026
4355f5c
test: add test for delayed share validate
icewind1991 Feb 16, 2026
f49069f
fix: disable share resolve postpone in tests
icewind1991 Feb 17, 2026
f6fc152
feat: export getData for public FileInfo interface
icewind1991 Feb 17, 2026
3665279
fix: clear in-memory cached mounts for user when adding/removing mounts
icewind1991 Feb 18, 2026
0bead2d
test: add reusable mock implementation for IAppConfig
icewind1991 Feb 19, 2026
a7c9bfe
test: add reusable mock implementation for IUserConfig
icewind1991 Feb 19, 2026
a625518
test: add some tests for SharesUpdatedListenerTest
icewind1991 Feb 19, 2026
bf6f6e2
test: add some tests for ShareRecipientUpdaterTest
icewind1991 Feb 19, 2026
81d2cef
feat: use time-based cutoff for share updating instead of count
icewind1991 Feb 20, 2026
ef04496
fix: improve performance of handling delete shares
icewind1991 Feb 25, 2026
95d6b5f
feat: add output options and '--cached-only' to list mounts command
icewind1991 Feb 25, 2026
9fee70b
fix: update shares on group delete
icewind1991 Feb 25, 2026
99fe6bd
test: add more integration tests for share mount handling
icewind1991 Feb 25, 2026
06d9b36
fix: handle share moves
icewind1991 Mar 12, 2026
283206b
fix: fix moving mountpoints
icewind1991 Mar 16, 2026
5fb9314
fix: move mountpoint when transfering share
icewind1991 Mar 16, 2026
e45bfd5
fix: use proper index when deleting mounts
icewind1991 Mar 27, 2026
9ac429c
fix: default user_needs_share_refresh to true
icewind1991 Mar 27, 2026
1f6fa18
test: add test for UserHomeSetupListener
icewind1991 Apr 2, 2026
4a7c478
fix: log when user is marked as needing share mount refresh
icewind1991 Apr 2, 2026
28b69b9
fix: add optional user param to IUserMountCache::removeMount
icewind1991 Apr 9, 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
16 changes: 14 additions & 2 deletions apps/files/lib/Service/OwnershipTransferService.php
Original file line number Diff line number Diff line change
Expand Up @@ -577,14 +577,16 @@ private function restoreShares(
$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 @@ -623,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 @@ -642,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 @@ -656,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
2 changes: 2 additions & 0 deletions apps/files_sharing/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
'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 @@ -96,6 +97,7 @@
'OCA\\Files_Sharing\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
'OCA\\Files_Sharing\\Scanner' => $baseDir . '/../lib/Scanner.php',
'OCA\\Files_Sharing\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.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
2 changes: 2 additions & 0 deletions apps/files_sharing/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class ComposerStaticInitFiles_Sharing
'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 @@ -111,6 +112,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
'OCA\\Files_Sharing\\Scanner' => __DIR__ . '/..' . '/../lib/Scanner.php',
'OCA\\Files_Sharing\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.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
8 changes: 8 additions & 0 deletions apps/files_sharing/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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 @@ -45,6 +46,8 @@
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;
Expand All @@ -54,6 +57,7 @@
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;
Expand Down Expand Up @@ -119,7 +123,11 @@ function () use ($c) {
$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