Skip to content

Commit b729dc4

Browse files
Merge pull request #58057 from nextcloud/carl/perf-delete-share
perf(sharing): Avoid loading all shares from all users when unsharing
2 parents 2b5a26d + 4acb3b5 commit b729dc4

6 files changed

Lines changed: 216 additions & 43 deletions

File tree

‎apps/files_sharing/tests/CapabilitiesTest.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use OCP\IAppConfig;
1919
use OCP\IConfig;
2020
use OCP\IDateTimeZone;
21+
use OCP\IDBConnection;
2122
use OCP\IGroupManager;
2223
use OCP\IUserManager;
2324
use OCP\IUserSession;
@@ -92,6 +93,7 @@ private function getResults(array $map, array $typedMap = [], bool $federationEn
9293
$this->createMock(ShareDisableChecker::class),
9394
$this->createMock(IDateTimeZone::class),
9495
$appConfig,
96+
$this->createMock(IDBConnection::class),
9597
);
9698

9799
$cap = new Capabilities($config, $appConfig, $shareManager, $appManager);

‎apps/settings/tests/Settings/Admin/SharingTest.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
use OCP\IL10N;
1717
use OCP\IURLGenerator;
1818
use OCP\Share\IManager;
19+
use PHPUnit\Framework\Attributes\Group;
1920
use PHPUnit\Framework\MockObject\MockObject;
2021
use Test\TestCase;
2122

23+
#[Group(name: 'DB')]
2224
class SharingTest extends TestCase {
2325
private Sharing $admin;
2426

‎lib/private/Share20/DefaultShareProvider.php‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,11 +1079,10 @@ public function getShareByToken($token) {
10791079
/**
10801080
* Create a share object from a database row
10811081
*
1082-
* @param mixed[] $data
1083-
* @return IShare
1082+
* @param array<string, mixed> $data
10841083
* @throws InvalidShare
10851084
*/
1086-
private function createShare($data) {
1085+
private function createShare($data): IShare {
10871086
$share = new Share($this->rootFolder, $this->userManager);
10881087
$share->setId($data['id'])
10891088
->setShareType((int)$data['share_type'])

‎lib/private/Share20/Manager.php‎

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use OCA\Files_Sharing\SharedStorage;
2020
use OCA\ShareByMail\ShareByMailProvider;
2121
use OCP\Constants;
22+
use OCP\DB\QueryBuilder\IQueryBuilder;
2223
use OCP\EventDispatcher\Event;
2324
use OCP\EventDispatcher\IEventDispatcher;
2425
use OCP\Files\File;
@@ -32,6 +33,7 @@
3233
use OCP\IAppConfig;
3334
use OCP\IConfig;
3435
use OCP\IDateTimeZone;
36+
use OCP\IDBConnection;
3537
use OCP\IGroupManager;
3638
use OCP\IL10N;
3739
use OCP\IUser;
@@ -90,6 +92,7 @@ public function __construct(
9092
private ShareDisableChecker $shareDisableChecker,
9193
private IDateTimeZone $dateTimeZone,
9294
private IAppConfig $appConfig,
95+
private IDBConnection $connection,
9396
) {
9497
$this->l = $this->l10nFactory->get('lib');
9598
// The constructor of LegacyHooks registers the listeners of share events
@@ -1039,35 +1042,76 @@ protected function promoteReshares(IShare $share): void {
10391042
IShare::TYPE_EMAIL,
10401043
];
10411044

1042-
foreach ($userIds as $userId) {
1043-
foreach ($shareTypes as $shareType) {
1045+
// Figure out which users has some shares with which providers
1046+
$qb = $this->connection->getQueryBuilder();
1047+
$qb->select('uid_initiator', 'share_type', 'uid_owner', 'file_source')
1048+
->from('share')
1049+
->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
1050+
->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter($shareTypes, IQueryBuilder::PARAM_INT_ARRAY)))
1051+
->andWhere(
1052+
$qb->expr()->orX(
1053+
$qb->expr()->in('uid_initiator', $qb->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)),
1054+
// Special case for old shares created via the web UI
1055+
$qb->expr()->andX(
1056+
$qb->expr()->in('uid_owner', $qb->createNamedParameter($userIds, IQueryBuilder::PARAM_STR_ARRAY)),
1057+
$qb->expr()->isNull('uid_initiator')
1058+
)
1059+
)
1060+
);
1061+
1062+
if (!$node instanceof Folder) {
1063+
$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId(), IQueryBuilder::PARAM_INT)));
1064+
}
1065+
1066+
$qb->orderBy('id');
1067+
1068+
$cursor = $qb->executeQuery();
1069+
/** @var array<string, list<array{IShare::TYPE_*, Node}>> $rawShare */
1070+
$rawShares = [];
1071+
while ($data = $cursor->fetch()) {
1072+
if (!isset($rawShares[$data['uid_initiator']])) {
1073+
$rawShares[$data['uid_initiator']] = [];
1074+
}
1075+
if (!in_array($data['share_type'], $rawShares[$data['uid_initiator']], true)) {
1076+
if ($node instanceof Folder) {
1077+
if ($data['file_source'] === null || $data['uid_owner'] === null) {
1078+
/* Ignore share of non-existing node */
1079+
continue;
1080+
}
1081+
1082+
// for federated shares the owner can be a remote user, in this
1083+
// case we use the initiator
1084+
if ($this->userManager->userExists($data['uid_owner'])) {
1085+
$userFolder = $this->rootFolder->getUserFolder($data['uid_owner']);
1086+
} else {
1087+
$userFolder = $this->rootFolder->getUserFolder($data['uid_initiator']);
1088+
}
1089+
$sharedNode = $userFolder->getFirstNodeById((int)$data['file_source']);
1090+
if (!$sharedNode) {
1091+
continue;
1092+
}
1093+
if ($node->getRelativePath($sharedNode->getPath()) !== null) {
1094+
$rawShares[$data['uid_initiator']][] = [(int)$data['share_type'], $sharedNode];
1095+
}
1096+
} elseif ($node instanceof File) {
1097+
$rawShares[$data['uid_initiator']][] = [(int)$data['share_type'], $node];
1098+
}
1099+
}
1100+
}
1101+
$cursor->closeCursor();
1102+
1103+
foreach ($rawShares as $userId => $shareInfos) {
1104+
foreach ($shareInfos as $shareInfo) {
1105+
[$shareType, $sharedNode] = $shareInfo;
10441106
try {
10451107
$provider = $this->factory->getProviderForType($shareType);
1046-
} catch (ProviderException $e) {
1108+
} catch (ProviderException) {
10471109
continue;
10481110
}
10491111

1050-
if ($node instanceof Folder) {
1051-
/* We need to get all shares by this user to get subshares */
1052-
$shares = $provider->getSharesBy($userId, $shareType, null, false, -1, 0);
1053-
1054-
foreach ($shares as $share) {
1055-
try {
1056-
$path = $share->getNode()->getPath();
1057-
} catch (NotFoundException) {
1058-
/* Ignore share of non-existing node */
1059-
continue;
1060-
}
1061-
if ($node->getRelativePath($path) !== null) {
1062-
/* If relative path is not null it means the shared node is the same or in a subfolder */
1063-
$reshareRecords[] = $share;
1064-
}
1065-
}
1066-
} else {
1067-
$shares = $provider->getSharesBy($userId, $shareType, $node, false, -1, 0);
1068-
foreach ($shares as $child) {
1069-
$reshareRecords[] = $child;
1070-
}
1112+
$shares = $provider->getSharesBy($userId, $shareType, $sharedNode, false, -1, 0);
1113+
foreach ($shares as $child) {
1114+
$reshareRecords[] = $child;
10711115
}
10721116
}
10731117
}

‎tests/lib/Share20/LegacyHooksTest.php‎

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
use OC\EventDispatcher\EventDispatcher;
1111
use OC\Share20\LegacyHooks;
12-
use OC\Share20\Manager;
1312
use OCP\Constants;
1413
use OCP\EventDispatcher\IEventDispatcher;
1514
use OCP\Files\Cache\ICacheEntry;
@@ -24,6 +23,7 @@
2423
use OCP\Share\IManager as IShareManager;
2524
use OCP\Share\IShare;
2625
use OCP\Util;
26+
use PHPUnit\Framework\Attributes\Group;
2727
use Psr\Log\LoggerInterface;
2828
use Test\TestCase;
2929

@@ -40,15 +40,11 @@ public function pre() {
4040
}
4141
}
4242

43+
#[Group(name: 'DB')]
4344
class LegacyHooksTest extends TestCase {
44-
/** @var LegacyHooks */
45-
private $hooks;
46-
47-
/** @var IEventDispatcher */
48-
private $eventDispatcher;
49-
50-
/** @var Manager */
51-
private $manager;
45+
private LegacyHooks $hooks;
46+
private IEventDispatcher $eventDispatcher;
47+
private IShareManager $manager;
5248

5349
protected function setUp(): void {
5450
parent::setUp();

0 commit comments

Comments
 (0)