Skip to content

Commit 83810ae

Browse files
authored
Merge pull request #57926 from nextcloud/share-target-repair-no-providers
fix: don't rely on share providers being avaiable in CleanupShareTarget
2 parents c373b8e + da6bf8b commit 83810ae

File tree

2 files changed

+60
-30
lines changed

2 files changed

+60
-30
lines changed

apps/files_sharing/lib/Repair/CleanupShareTarget.php

Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,18 @@
1313
use OCP\DB\QueryBuilder\IQueryBuilder;
1414
use OCP\Files\Config\ICachedMountInfo;
1515
use OCP\Files\Config\IUserMountCache;
16+
use OCP\Files\IRootFolder;
17+
use OCP\Files\NotFoundException;
1618
use OCP\IDBConnection;
1719
use OCP\IUserManager;
1820
use OCP\Migration\IOutput;
1921
use OCP\Migration\IRepairStep;
20-
use OCP\Share\IManager;
21-
use OCP\Share\IProviderFactory;
2222
use OCP\Share\IShare;
23+
use Psr\Log\LoggerInterface;
2324

25+
/**
26+
* @psalm-type ShareInfo = array{id: string|int, share_type: string, share_with: string, file_source: string, file_target: string}
27+
*/
2428
class CleanupShareTarget implements IRepairStep {
2529
/** we only care about shares with a user target,
2630
* since the underling group/deck/talk share doesn't get moved
@@ -34,12 +38,12 @@ class CleanupShareTarget implements IRepairStep {
3438

3539
public function __construct(
3640
private readonly IDBConnection $connection,
37-
private readonly IManager $shareManager,
38-
private readonly IProviderFactory $shareProviderFactory,
3941
private readonly ShareTargetValidator $shareTargetValidator,
4042
private readonly IUserManager $userManager,
4143
private readonly SetupManager $setupManager,
4244
private readonly IUserMountCache $userMountCache,
45+
private readonly IRootFolder $rootFolder,
46+
private readonly LoggerInterface $logger,
4347
) {
4448
}
4549

@@ -58,12 +62,13 @@ public function run(IOutput $output) {
5862

5963
$lastUser = '';
6064
$userMounts = [];
65+
$userFolder = null;
6166

6267
foreach ($this->getProblemShares() as $shareInfo) {
6368
$recipient = $this->userManager->getExistingUser($shareInfo['share_with']);
64-
$share = $this->shareProviderFactory
65-
->getProviderForType((int)$shareInfo['share_type'])
66-
->getShareById($shareInfo['id'], $recipient->getUID());
69+
if (!$recipient->isEnabled()) {
70+
continue;
71+
}
6772

6873
// since we ordered the share by user, we can reuse the last data until we get to the next user
6974
if ($lastUser !== $recipient->getUID()) {
@@ -74,24 +79,39 @@ public function run(IOutput $output) {
7479
$mounts = $this->userMountCache->getMountsForUser($recipient);
7580
$mountPoints = array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $mounts);
7681
$userMounts = array_combine($mountPoints, $mounts);
82+
$userFolder = $this->rootFolder->getUserFolder($recipient->getUID());
7783
}
7884

79-
$oldTarget = $share->getTarget();
85+
$oldTarget = $shareInfo['file_target'];
8086
$newTarget = $this->cleanTarget($oldTarget);
81-
$share->setTarget($newTarget);
82-
$this->shareManager->moveShare($share, $recipient->getUID());
83-
84-
$this->shareTargetValidator->verifyMountPoint(
85-
$recipient,
86-
$share,
87-
$userMounts,
88-
[$share],
89-
);
87+
$absoluteNewTarget = $userFolder->getFullPath($newTarget);
88+
try {
89+
$targetParentNode = $this->rootFolder->get(dirname($absoluteNewTarget));
90+
} catch (NotFoundException) {
91+
$absoluteNewTarget = $userFolder->getFullPath(basename($newTarget));
92+
$targetParentNode = $userFolder;
93+
}
9094

91-
$oldMountPoint = "/{$recipient->getUID()}/files$oldTarget/";
92-
$newMountPoint = "/{$recipient->getUID()}/files$newTarget/";
93-
$userMounts[$newMountPoint] = $userMounts[$oldMountPoint];
94-
unset($userMounts[$oldMountPoint]);
95+
try {
96+
$absoluteNewTarget = $this->shareTargetValidator->generateUniqueTarget(
97+
(int)$shareInfo['file_source'],
98+
$absoluteNewTarget,
99+
$targetParentNode->getMountPoint(),
100+
$userMounts,
101+
);
102+
$newTarget = $userFolder->getRelativePath($absoluteNewTarget);
103+
104+
$this->moveShare((string)$shareInfo['id'], $newTarget);
105+
106+
$oldMountPoint = "/{$recipient->getUID()}/files$oldTarget/";
107+
$newMountPoint = "/{$recipient->getUID()}/files$newTarget/";
108+
$userMounts[$newMountPoint] = $userMounts[$oldMountPoint];
109+
unset($userMounts[$oldMountPoint]);
110+
} catch (\Exception $e) {
111+
$msg = 'error cleaning up share target: ' . $e->getMessage();
112+
$this->logger->error($msg, ['exception' => $e, 'app' => 'files_sharing']);
113+
$output->warning($msg);
114+
}
95115

96116
$output->advance();
97117
}
@@ -108,19 +128,29 @@ private function countProblemShares(): int {
108128
return (int)$query->executeQuery()->fetchOne();
109129
}
110130

131+
private function moveShare(string $id, string $target): void {
132+
// since we only process user-specific shares, we can just move them
133+
// without having to check if we need to create a user-specific override
134+
$query = $this->connection->getQueryBuilder();
135+
$query->update('share')
136+
->set('file_target', $query->createNamedParameter($target))
137+
->where($query->expr()->eq('id', $query->createNamedParameter($id)))
138+
->executeStatement();
139+
}
140+
111141
/**
112-
* @return \Traversable<array{id: string, share_type: string, share_with: string}>
142+
* @return \Traversable<ShareInfo>
113143
*/
114144
private function getProblemShares(): \Traversable {
115145
$query = $this->connection->getQueryBuilder();
116-
$query->select('id', 'share_type', 'share_with')
146+
$query->select('id', 'share_type', 'share_with', 'file_source', 'file_target')
117147
->from('share')
118148
->where($query->expr()->like('file_target', $query->createNamedParameter('% (_) (_)%')))
119149
->andWhere($query->expr()->in('share_type', $query->createNamedParameter(self::USER_SHARE_TYPES, IQueryBuilder::PARAM_INT_ARRAY), IQueryBuilder::PARAM_INT_ARRAY))
120150
->orderBy('share_with')
121151
->addOrderBy('id');
122152
$result = $query->executeQuery();
123-
/** @var \Traversable<array{id: string, share_type: string, share_with: string}> $rows */
153+
/** @var \Traversable<ShareInfo> $rows */
124154
$rows = $result->iterateAssociative();
125155
return $rows;
126156
}

apps/files_sharing/lib/ShareTargetValidator.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public function verifyMountPoint(
8585
}
8686

8787
$newAbsoluteMountPoint = $this->generateUniqueTarget(
88-
$share,
88+
$share->getNodeId(),
8989
Filesystem::normalizePath($absoluteParent . '/' . $mountPoint),
9090
$parentMount,
9191
$allCachedMounts,
@@ -108,8 +108,8 @@ public function verifyMountPoint(
108108
/**
109109
* @param ICachedMountInfo[] $allCachedMounts
110110
*/
111-
private function generateUniqueTarget(
112-
IShare $share,
111+
public function generateUniqueTarget(
112+
int $shareNodeId,
113113
string $absolutePath,
114114
IMountPoint $parentMount,
115115
array $allCachedMounts,
@@ -122,7 +122,7 @@ private function generateUniqueTarget(
122122
$i = 2;
123123
$parentCache = $parentMount->getStorage()->getCache();
124124
$internalPath = $parentMount->getInternalPath($absolutePath);
125-
while ($parentCache->inCache($internalPath) || $this->hasConflictingMount($share, $allCachedMounts, $absolutePath)) {
125+
while ($parentCache->inCache($internalPath) || $this->hasConflictingMount($shareNodeId, $allCachedMounts, $absolutePath)) {
126126
$absolutePath = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
127127
$internalPath = $parentMount->getInternalPath($absolutePath);
128128
$i++;
@@ -134,13 +134,13 @@ private function generateUniqueTarget(
134134
/**
135135
* @param ICachedMountInfo[] $allCachedMounts
136136
*/
137-
private function hasConflictingMount(IShare $share, array $allCachedMounts, string $absolutePath): bool {
137+
private function hasConflictingMount(int $shareNodeId, array $allCachedMounts, string $absolutePath): bool {
138138
if (!isset($allCachedMounts[$absolutePath . '/'])) {
139139
return false;
140140
}
141141

142142
$mount = $allCachedMounts[$absolutePath . '/'];
143-
if ($mount->getMountProvider() === MountProvider::class && $mount->getRootId() === $share->getNodeId()) {
143+
if ($mount->getMountProvider() === MountProvider::class && $mount->getRootId() === $shareNodeId) {
144144
// "conflicting" mount is a mount for the current share
145145
return false;
146146
}

0 commit comments

Comments
 (0)