Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
59 changes: 0 additions & 59 deletions lib/Db/MountPointRequest.php

This file was deleted.

76 changes: 0 additions & 76 deletions lib/Db/MountPointRequestBuilder.php

This file was deleted.

7 changes: 4 additions & 3 deletions lib/Db/MountRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use OCA\Circles\Exceptions\RequestBuilderException;
use OCA\Circles\IFederatedUser;
use OCA\Circles\Model\Mount;
use OCA\Circles\Repository\MountpointRepository;
use OCA\Circles\Service\ConfigService;
use OCA\Circles\Service\TimezoneService;
use OCA\Circles\Tools\Traits\TStringTools;
Expand All @@ -28,7 +29,7 @@ class MountRequest extends MountRequestBuilder {
public function __construct(
TimezoneService $timezoneService,
ConfigService $configService,
private MountPointRequest $mountPointRequest,
private MountpointRepository $mountpointRepository,
) {
parent::__construct($timezoneService, $configService);
}
Expand Down Expand Up @@ -61,7 +62,7 @@ public function delete(string $token): void {
$qb->executeStatement();

foreach ($mounts as $mount) {
$this->mountPointRequest->deleteByMountId($mount->getMountId());
$this->mountpointRepository->deleteBy(['mountId' => $mount->getMountId()]);
}
}

Expand All @@ -83,7 +84,7 @@ public function deleteByCircleId(string $circleId): void {
$qb->executeStatement();

foreach ($mounts as $mount) {
$this->mountPointRequest->deleteByMountId($mount->getMountId());
$this->mountpointRepository->deleteBy(['mountId' => $mount->getMountId()]);
}
}

Expand Down
49 changes: 49 additions & 0 deletions lib/Entity/Mountpoint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Circles\Entity;

use JsonSerializable;
use OCP\AppFramework\ORM\Attribute\Column;
use OCP\AppFramework\ORM\Attribute\Entity;
use OCP\AppFramework\ORM\Attribute\Id;
use OCP\DB\Schema\ColumnType;

/**
* The filesystem mountpoint a member sees a circle-shared mount under, when it differs from the
* mount's original mountpoint (e.g. to avoid a filename clash).
*
* Maps to the `circles_mountpoint` table. `mountpointHash` is not app-facing data: it mirrors
* `mountPoint` (md5, or empty for the placeholder value '-') purely so the DB can enforce
* uniqueness per member via an index - callers should not need to read or set it directly.
*/
#[Entity(name: 'circles_mountpoint')]
final class Mountpoint implements JsonSerializable {
#[Id]
#[Column(name: 'mount_id', type: ColumnType::String, length: 31)]
public string $mountId = '';

#[Id]
#[Column(name: 'single_id', type: ColumnType::String, length: 31)]
public string $singleId = '';

#[Column(name: 'mountpoint', type: ColumnType::Text)]
public string $mountPoint = '';

#[Column(name: 'mountpoint_hash', type: ColumnType::String, length: 64)]
public string $mountpointHash = '';

public function jsonSerialize(): array {
return [
'mountId' => $this->mountId,
'singleId' => $this->singleId,
'mountPoint' => $this->mountPoint,
];
}
}
13 changes: 0 additions & 13 deletions lib/Exceptions/MountPointNotFoundException.php

This file was deleted.

111 changes: 111 additions & 0 deletions lib/Migration/Version360000Date20260922140000.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Circles\Migration;

use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\Migration\Attributes\AddIndex;
use OCP\Migration\Attributes\ColumnType;
use OCP\Migration\Attributes\DropColumn;
use OCP\Migration\Attributes\DropIndex;
use OCP\Migration\Attributes\IndexType;
use OCP\Migration\Attributes\ModifyColumn;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
use Override;

/**
* (mount_id, single_id) is the real natural key of circles_mountpoint: at most one alternate
* mountpoint per member per mount. Nothing ever enforced that, so the composite key is made
* primary here, replacing the surrogate autoincrement id.
*/
#[ModifyColumn(table: 'circles_mountpoint', name: 'mount_id', type: ColumnType::STRING)]
#[ModifyColumn(table: 'circles_mountpoint', name: 'single_id', type: ColumnType::STRING)]
#[DropIndex(table: 'circles_mountpoint', type: IndexType::INDEX, description: 'circles_mountpoint_ms is redundant with the new primary key')]
#[DropColumn(table: 'circles_mountpoint', name: 'id')]
#[AddIndex(table: 'circles_mountpoint', type: IndexType::PRIMARY)]
final class Version360000Date20260922140000 extends SimpleMigrationStep {
public function __construct(
private readonly IDBConnection $connection,
) {
}

#[Override]
public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
$delete = $this->connection->getQueryBuilder();
$delete->delete('circles_mountpoint')
->where($delete->expr()->orX(
$delete->expr()->isNull('mount_id'),
$delete->expr()->eq('mount_id', $delete->createNamedParameter('')),
$delete->expr()->isNull('single_id'),
$delete->expr()->eq('single_id', $delete->createNamedParameter('')),
));
$delete->executeStatement();

// A few installs may have raced two writers into inserting two rows for the same
// (mount_id, single_id) before this constraint existed; keep the oldest one.
$select = $this->connection->getQueryBuilder();
$select->select('id', 'mount_id', 'single_id')
->from('circles_mountpoint')
->orderBy('id', 'ASC');
$result = $select->executeQuery();

$seen = [];
$duplicateIds = [];
while ($row = $result->fetch()) {
$key = $row['mount_id'] . "\0" . $row['single_id'];
if (isset($seen[$key])) {
$duplicateIds[] = (int)$row['id'];
continue;
}
$seen[$key] = true;
}
$result->closeCursor();

foreach ($duplicateIds as $id) {
$delete = $this->connection->getQueryBuilder();
$delete->delete('circles_mountpoint')
->where($delete->expr()->eq('id', $delete->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
$delete->executeStatement();
}
}

#[Override]
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

if (!$schema->hasTable('circles_mountpoint')) {
return null;
}

$table = $schema->getTable('circles_mountpoint');
$table->modifyColumn('mount_id', ['notnull' => true, 'default' => '']);
$table->modifyColumn('single_id', ['notnull' => true, 'default' => '']);

if ($table->hasIndex('circles_mountpoint_ms')) {
$table->dropIndex('circles_mountpoint_ms');
}

if ($table->hasPrimaryKey()) {
$table->dropPrimaryKey();
}

if ($table->hasColumn('id')) {
$table->dropColumn('id');
}

$table->setPrimaryKey(['mount_id', 'single_id']);

return $schema;
}
}
15 changes: 9 additions & 6 deletions lib/Model/ModelManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
use OCA\Circles\Db\MemberRequest;
use OCA\Circles\Db\MembershipRequest;
use OCA\Circles\Entity\CircleInvitation;
use OCA\Circles\Entity\Mountpoint;
use OCA\Circles\Entity\ShareToken;
use OCA\Circles\Exceptions\CircleNotFoundException;
use OCA\Circles\Exceptions\FederatedItemException;
use OCA\Circles\Exceptions\FederatedUserNotFoundException;
use OCA\Circles\Exceptions\FileCacheNotFoundException;
use OCA\Circles\Exceptions\MemberNotFoundException;
use OCA\Circles\Exceptions\MembershipNotFoundException;
use OCA\Circles\Exceptions\MountPointNotFoundException;
use OCA\Circles\Exceptions\OwnerNotFoundException;
use OCA\Circles\Exceptions\RemoteInstanceException;
use OCA\Circles\Exceptions\RemoteNotFoundException;
Expand Down Expand Up @@ -457,12 +457,15 @@ private function importIntoMount(
break;

case CoreQueryBuilder::MOUNTPOINT:
try {
$mountPoint = new Mountpoint();
$mountPoint->importFromDatabase($data, $prefix);
$mount->setAlternateMountPoint($mountPoint);
} catch (MountPointNotFoundException) {
if (($data[$prefix . 'mountpoint'] ?? '') === '') {
break;
}

$mountPoint = new Mountpoint();
$mountPoint->mountId = $data[$prefix . 'mount_id'] ?? '';
$mountPoint->singleId = $data[$prefix . 'single_id'] ?? '';
$mountPoint->mountPoint = $data[$prefix . 'mountpoint'] ?? '';
$mount->setAlternateMountPoint($mountPoint);
break;
}
}
Expand Down
Loading
Loading