Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
313bce7
fix: only validate mounts for new share
icewind1991 Jan 23, 2026
0abcd0c
feat: add event for user home mount having being setup
icewind1991 Feb 12, 2026
18e6ebc
chore: move share recipient validation logic to a separate class
icewind1991 Feb 12, 2026
8858ee9
feat: postpone receiving share validation after processing a certain …
icewind1991 Feb 12, 2026
92861a7
test: add test for delayed share validate
icewind1991 Feb 16, 2026
c97ea9d
fix: disable share resolve postpone in tests
icewind1991 Feb 17, 2026
0107554
feat: export getData for public FileInfo interface
icewind1991 Feb 17, 2026
da92a0e
fix: clear in-memory cached mounts for user when adding/removing mounts
icewind1991 Feb 18, 2026
c0f06ff
test: add reusable mock implementation for IAppConfig
icewind1991 Feb 19, 2026
e3bf1bf
test: add reusable mock implementation for IUserConfig
icewind1991 Feb 19, 2026
ba0a28a
test: add some tests for SharesUpdatedListenerTest
icewind1991 Feb 19, 2026
3016741
test: add some tests for ShareRecipientUpdaterTest
icewind1991 Feb 19, 2026
9bce128
feat: use time-based cutoff for share updating instead of count
icewind1991 Feb 20, 2026
d4ede7b
fix: improve performance of handling delete shares
icewind1991 Feb 25, 2026
928fcda
feat: add output options and '--cached-only' to list mounts command
icewind1991 Feb 25, 2026
cd78144
fix: update shares on group delete
icewind1991 Feb 25, 2026
eba7f67
test: add more integration tests for share mount handling
icewind1991 Feb 25, 2026
dc814d6
fix: handle share moves
icewind1991 Mar 12, 2026
4eb3c19
fix: fix moving mountpoints
icewind1991 Mar 16, 2026
0c1aa07
fix: move mountpoint when transfering share
icewind1991 Mar 16, 2026
ed1b874
fix: use proper index when deleting mounts
icewind1991 Mar 27, 2026
b8dbb47
fix: default user_needs_share_refresh to true
icewind1991 Mar 27, 2026
6f02d77
test: add test for UserHomeSetupListener
icewind1991 Apr 2, 2026
4b054cb
fix: log when user is marked as needing share mount refresh
icewind1991 Apr 2, 2026
02b6245
fix: add optional user param to IUserMountCache::removeMount
icewind1991 Apr 9, 2026
ff66c51
fix: fix LazyUserFolder::getMountPoint
icewind1991 Apr 10, 2026
4701894
fix: check share target parent in userfolders mount
icewind1991 Apr 10, 2026
92d20c3
fix: don't trigger on-setup share update from inside the share listener
icewind1991 Apr 9, 2026
bfff87a
fix: fix UserHomeSetupListener disabling with nested events
icewind1991 Apr 10, 2026
e0edff8
perf: don't fetch child mounts when getting node parent
icewind1991 Apr 10, 2026
149bbf7
perf: only load a single mount at a time when checking for share conf…
icewind1991 Apr 14, 2026
4c0a598
perf: only load possible results in UserMountCache::getMountForPath
icewind1991 Apr 14, 2026
9e91272
test: more tests for UserMountCache
icewind1991 Apr 14, 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