Skip to content

Unified Sharing Legacy Sync - #65972

Draft
provokateurin wants to merge 22 commits into
masterfrom
unified-sharing-legacy-sync
Draft

provokateurin wants to merge 22 commits into
masterfrom
unified-sharing-legacy-sync

Conversation

@provokateurin

Copy link
Copy Markdown
Member

#64737

There are some TODOs left in the code, but I just need to get this merged, so future changes can be done in smaller PRs.

The syncing logic is not nice to understand, because it's an encoding of all the quirks and limitations the legacy sharing system has. I tried my best to leave useful comments whenever possible, but I'm happy to add more if there are questions during the review.

… the controller

Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
… a hash already

Signed-off-by: provokateurin <kate@provokateurin.de>
…ead of load

Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
…ailable

Signed-off-by: provokateurin <kate@provokateurin.de>
… unit tests

Signed-off-by: provokateurin <kate@provokateurin.de>
…licit recipients

Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
Signed-off-by: provokateurin <kate@provokateurin.de>
@provokateurin provokateurin added this to the Nextcloud 36 milestone Oct 27, 2026
@provokateurin provokateurin self-assigned this Oct 27, 2026
$share->getShareType(),
$share->getPermissions()
);
if (defined('PHPUNIT_RUN')) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo it's better to have a sendNotifications or something property and have the tests set that to false.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, maybe better, mock the $this->notifications


$sourceNodeTargetTable = $schema->createTable('sharing_source_node_target');
$sourceNodeTargetTable->addColumn('user_id', Types::TEXT, ['length' => 64]);
$sourceNodeTargetTable->addColumn('source_instance', Types::STRING, ['length' => 128, 'notnull' => false]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this and any code using it will need to be adjusted once #65512 is in


#[Override]
public function handle(Event $event): void {
foreach ($event->shareIds as $shareId) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we send the share objects with the event to prevent having to re-fetch them here.

Comment on lines +72 to +73
$ownerUserFolder = $this->rootFolder->getUserFolder($owner->userId);
$node = $ownerUserFolder->getFirstNodeById($sourceNodeId);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this function is called in a loop for the same share, the caller should get this once and pass it

Comment on lines +63 to +72
foreach (array_keys($userIds) as $userId) {
try {
if ($this->sourceNodeTargetManager->getTarget($userId, $share->owner, (int)$source->value) === null) {
$this->sourceNodeTargetManager->createDefaultTarget($userId, $share->owner, (int)$source->value);
}
} catch (NotFoundException) {
// The same exception will happen for the other users, because the source node is the same, so we can just stop here.
break;
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should use getTargets, which both fetches all existing targets at once instead of one by one, and has built-in "create default" handling.

/**
* Get user ids for a recipient value.
*
* @return list<string>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer list<IUser>

if (!$share->getNoExpirationDate() || $isEnforced) {
if ($expirationDate !== null) {
$expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
$expirationDate->setTime(23, 59, 59);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this behavior change intended?


$legacyMappingTable = $schema->createTable('sharing_share_legacy_mapping');
$legacyMappingTable->addColumn('id', Types::BIGINT);
$legacyMappingTable->addColumn('legacy_provider', Types::STRING, ['length' => 128]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use class names and the class-mapper so we can store this as ints?

$legacyMappingTable->addColumn('recipient_secrets', Types::TEXT);
$legacyMappingTable->addColumn('recipient_value', Types::STRING, ['length' => 255]);
$legacyMappingTable->setPrimaryKey(['legacy_provider', 'legacy_id']);
$legacyMappingTable->addIndex(['id']);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is id + recipient_value unique? if so, we should probably have an index for it

* @experimental 35.0.0
*/
public function getCreatedAt(): DateTimeImmutable {
return $this->createdAt ??= Server::get(ISnowflakeDecoder::class)->decode($this->id)->getCreatedAt();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inject the decoder in the constructor instead?

}

// TODO: Test
public function mapLegacyShares(?IUser $user, ?int $limit = null): void {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapUnmappedLegacyShares to make it more clear that it doesn't update existing mappings

Comment on lines +169 to +178
$legacyMapping = $this->legacyMapper->createLegacyMapping(
$id,
$provider,
$row['id'],
new DateTimeImmutable(),
[$this->getSharingManager()->generateSecret()],
$this->getSharingManager()->generateSecret(),
);

$this->syncLegacySharesToShare($id, [$legacyMapping]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these two should be in a transaction to prevent an error halfway from causing a desync

if ($event instanceof SharesUpdatedEvent) {
foreach ($event->shareIds as $id) {
$this->wrapSync($id, function () use ($id): void {
$share = $this->getSharingManager()->getShare(new ShareAccessContext(overrideChecks: true), $id);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another place where having the share in the event would save work

Comment on lines +447 to +450
new ShareUser(
$shareInitiator['value'],
$shareInitiator['instance'],
),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nitpick: creating the ShareUser can be de-duplicated

Comment on lines +914 to +918
$this->ignoreNewLegacyShare = false;
} catch (Exception $exception) {
$this->ignoreNewLegacyShare = false;
throw $exception;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} finally {
    $this->ignoreNewLegacyShare = false;
}

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants