Skip to content

Commit 70ecabc

Browse files
committed
fix: add repair step for cleanup shares with excess (2)
Signed-off-by: Robin Appelman <robin@icewind.nl>
1 parent d0d38e4 commit 70ecabc

File tree

4 files changed

+136
-0
lines changed

4 files changed

+136
-0
lines changed

apps/files_sharing/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
'OCA\\Files_Sharing\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
9292
'OCA\\Files_Sharing\\OpenMetrics\\SharesCountMetric' => $baseDir . '/../lib/OpenMetrics/SharesCountMetric.php',
9393
'OCA\\Files_Sharing\\OrphanHelper' => $baseDir . '/../lib/OrphanHelper.php',
94+
'OCA\\Files_Sharing\\Repair\\CleanupShareTarget' => $baseDir . '/../lib/Repair/CleanupShareTarget.php',
9495
'OCA\\Files_Sharing\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
9596
'OCA\\Files_Sharing\\Scanner' => $baseDir . '/../lib/Scanner.php',
9697
'OCA\\Files_Sharing\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',

apps/files_sharing/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ class ComposerStaticInitFiles_Sharing
106106
'OCA\\Files_Sharing\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
107107
'OCA\\Files_Sharing\\OpenMetrics\\SharesCountMetric' => __DIR__ . '/..' . '/../lib/OpenMetrics/SharesCountMetric.php',
108108
'OCA\\Files_Sharing\\OrphanHelper' => __DIR__ . '/..' . '/../lib/OrphanHelper.php',
109+
'OCA\\Files_Sharing\\Repair\\CleanupShareTarget' => __DIR__ . '/..' . '/../lib/Repair/CleanupShareTarget.php',
109110
'OCA\\Files_Sharing\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
110111
'OCA\\Files_Sharing\\Scanner' => __DIR__ . '/..' . '/../lib/Scanner.php',
111112
'OCA\\Files_Sharing\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Files_Sharing\Repair;
10+
11+
use OC\Files\SetupManager;
12+
use OCA\Files_Sharing\ShareTargetValidator;
13+
use OCP\DB\QueryBuilder\IQueryBuilder;
14+
use OCP\Files\Mount\IMountManager;
15+
use OCP\IDBConnection;
16+
use OCP\IUserManager;
17+
use OCP\Migration\IOutput;
18+
use OCP\Migration\IRepairStep;
19+
use OCP\Share\IManager;
20+
use OCP\Share\IProviderFactory;
21+
use OCP\Share\IShare;
22+
23+
class CleanupShareTarget implements IRepairStep {
24+
/** we only care about shares with a user target,
25+
* since the underling group/deck/talk share doesn't get moved
26+
*/
27+
private const USER_SHARE_TYPES = [
28+
IShare::TYPE_USER,
29+
IShare::TYPE_USERGROUP,
30+
IShare::TYPE_DECK_USER,
31+
11 // TYPE_USERROOM
32+
];
33+
34+
public function __construct(
35+
private readonly IDBConnection $connection,
36+
private readonly IManager $shareManager,
37+
private readonly IProviderFactory $shareProviderFactory,
38+
private readonly ShareTargetValidator $shareTargetValidator,
39+
private readonly IUserManager $userManager,
40+
private readonly SetupManager $setupManager,
41+
private readonly IMountManager $mountManager,
42+
) {
43+
}
44+
45+
#[\Override]
46+
public function getName() {
47+
return 'Cleanup share names with false conflicts';
48+
}
49+
50+
#[\Override]
51+
public function run(IOutput $output) {
52+
$count = $this->countProblemShares();
53+
if ($count === 0) {
54+
return;
55+
}
56+
$output->startProgress($count);
57+
58+
$lastUser = '';
59+
$userMounts = [];
60+
61+
foreach ($this->getProblemShares() as $shareInfo) {
62+
$recipient = $this->userManager->getExistingUser($shareInfo['share_with']);
63+
$share = $this->shareProviderFactory
64+
->getProviderForType($shareInfo['share_type'])
65+
->getShareById($shareInfo['id'], $recipient->getUID());
66+
67+
// since we ordered the share by user, we can reuse the last data until we get to the next user
68+
if ($lastUser !== $recipient->getUID()) {
69+
$lastUser = $recipient->getUID();
70+
71+
$this->setupManager->tearDown();
72+
$this->setupManager->setupForUser($recipient);
73+
$userMounts = $this->mountManager->getAll();
74+
}
75+
76+
$oldTarget = $share->getTarget();
77+
$newTarget = $this->cleanTarget($oldTarget);
78+
$share->setTarget($newTarget);
79+
$this->shareManager->moveShare($share, $recipient->getUID());
80+
81+
$this->shareTargetValidator->verifyMountPoint(
82+
$recipient,
83+
$share,
84+
$userMounts,
85+
[$share],
86+
);
87+
88+
$oldMountPoint = "/{$recipient->getUID()}/files$oldTarget/";
89+
$newMountPoint = "/{$recipient->getUID()}/files$newTarget/";
90+
$userMounts[$newMountPoint] = $userMounts[$oldMountPoint];
91+
unset($userMounts[$oldMountPoint]);
92+
93+
$output->advance();
94+
}
95+
$output->finishProgress();
96+
$output->info("Fixed $count shares");
97+
}
98+
99+
private function countProblemShares(): int {
100+
$query = $this->connection->getQueryBuilder();
101+
$query->select($query->func()->count('id'))
102+
->from('share')
103+
->where($query->expr()->like('file_target', $query->createNamedParameter('% (2) (2)%')))
104+
->andWhere($query->expr()->in('share_type', self::USER_SHARE_TYPES, IQueryBuilder::PARAM_INT_ARRAY));
105+
return $query->executeQuery()->fetchOne() ?: 0;
106+
}
107+
108+
/**
109+
* @return \Iterator<array{id: integer, share_type: integer, share_with: string}>
110+
*/
111+
private function getProblemShares(): \Iterator {
112+
$query = $this->connection->getQueryBuilder();
113+
$query->select('id', 'share_type', 'share_with')
114+
->from('share')
115+
->where($query->expr()->like('file_target', $query->createNamedParameter('% (2) (2)%')))
116+
->andWhere($query->expr()->in('share_type', self::USER_SHARE_TYPES, IQueryBuilder::PARAM_INT_ARRAY))
117+
->orderBy('share_with')
118+
->addOrderBy('id');
119+
$result = $query->executeQuery();
120+
while ($row = $result->fetchAssociative()) {
121+
yield [
122+
'id' => (int)$row['id'],
123+
'share_type' => (int)$row['share_type'],
124+
'share_with' => $row['share_with'],
125+
];
126+
}
127+
}
128+
129+
private function cleanTarget(string $target): string {
130+
return preg_replace('/ \(2\)+/', '', $target);
131+
}
132+
}

lib/private/Repair.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
use OC\Template\JSCombiner;
6161
use OCA\DAV\Migration\DeleteSchedulingObjects;
6262
use OCA\DAV\Migration\RemoveObjectProperties;
63+
use OCA\Files_Sharing\Repair\CleanupShareTarget;
6364
use OCP\AppFramework\QueryException;
6465
use OCP\AppFramework\Utility\ITimeFactory;
6566
use OCP\Collaboration\Resources\IManager;
@@ -221,6 +222,7 @@ public static function getExpensiveRepairSteps() {
221222
),
222223
\OCP\Server::get(DeleteSchedulingObjects::class),
223224
\OC::$server->get(RemoveObjectProperties::class),
225+
\OCP\Server::get(CleanupShareTarget::class),
224226
];
225227
}
226228

0 commit comments

Comments
 (0)