feat: M3 import & indexing engine delivery layer with durable collision handling, space checking and console
This commit is contained in:
1 parent
93d991703c
commit
9d277d9e72
48 files changed
+5147
-4
No files matched your search
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MeshVault\Repository;
|
||||
|
||||
use DomainException;
|
||||
use InvalidArgumentException;
|
||||
use PDO;
|
||||
|
||||
final class PdoCatalogRepository extends PdoRepository implements CatalogRepositoryInterface
|
||||
{
|
||||
public function ensureModel(string $folderPath, string $modelName): int
|
||||
{
|
||||
if ($modelName === '') {
|
||||
throw new InvalidArgumentException('Model name must not be empty.');
|
||||
}
|
||||
return $this->transaction(function () use ($folderPath, $modelName): int {
|
||||
$folder = $this->ensureFolder($folderPath);
|
||||
$this->execute(
|
||||
'INSERT INTO models (folder_id, name) VALUES (?, ?) ON CONFLICT (folder_id, name) DO NOTHING',
|
||||
[$folder, $modelName]
|
||||
);
|
||||
return (int) $this->row(
|
||||
'SELECT id FROM models WHERE folder_id = ? AND name = ?',
|
||||
[$folder, $modelName]
|
||||
)['id'];
|
||||
});
|
||||
}
|
||||
|
||||
public function recordFile(
|
||||
int $modelId,
|
||||
string $managedPath,
|
||||
string $hash,
|
||||
int $size,
|
||||
?string $mime = null,
|
||||
string $status = 'Present'
|
||||
): int {
|
||||
$this->validateStatus($status);
|
||||
if ($managedPath === '' || $hash === '' || $size < 0) {
|
||||
throw new InvalidArgumentException('A file requires a path, hash and non-negative size.');
|
||||
}
|
||||
return $this->transaction(function () use ($modelId, $managedPath, $hash, $size, $mime, $status): int {
|
||||
// Serialize catalog writers on the root row, including paths assigned to different models.
|
||||
$this->ensureFolder('/');
|
||||
$this->execute("UPDATE folders SET name = name WHERE path = '/'");
|
||||
$existing = $this->row('SELECT * FROM files WHERE managed_path = ?', [$managedPath]);
|
||||
if ($existing !== null) {
|
||||
if (
|
||||
(int) $existing['model_id'] !== $modelId || $existing['hash'] !== $hash
|
||||
|| (int) $existing['size'] !== $size
|
||||
) {
|
||||
throw new DomainException('Managed path already records a different file.');
|
||||
}
|
||||
return (int) $existing['id'];
|
||||
}
|
||||
$folder = $this->ensureFolder(dirname(str_replace('\\', '/', $managedPath)));
|
||||
$file = $this->row(
|
||||
'INSERT INTO files (model_id, folder_id, name, managed_path, hash, size, mime_type, status)'
|
||||
. ' VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING id',
|
||||
[$modelId, $folder, basename($managedPath), $managedPath, $hash, $size, $mime, $status]
|
||||
);
|
||||
return (int) $file['id'];
|
||||
});
|
||||
}
|
||||
|
||||
public function aggregate(int $modelId): ?array
|
||||
{
|
||||
return $this->transaction(function () use ($modelId): ?array {
|
||||
$model = $this->row('SELECT * FROM models WHERE id = ?', [$modelId]);
|
||||
if ($model === null) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
'model' => $model,
|
||||
'folder' => $this->row('SELECT * FROM folders WHERE id = ?', [$model['folder_id']]),
|
||||
'files' => $this->execute('SELECT * FROM files WHERE model_id = ? ORDER BY id', [$modelId])
|
||||
->fetchAll(PDO::FETCH_ASSOC),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function setFileStatus(int $fileId, string $status): void
|
||||
{
|
||||
$this->validateStatus($status);
|
||||
if (
|
||||
$this->execute(
|
||||
'UPDATE files SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||||
[$status, $fileId]
|
||||
)->rowCount() === 0
|
||||
) {
|
||||
throw new DomainException('Unknown file.');
|
||||
}
|
||||
}
|
||||
|
||||
private function validateStatus(string $status): void
|
||||
{
|
||||
if (!in_array($status, ['Present', 'Missing', 'Updated', 'Duplicate'], true)) {
|
||||
throw new InvalidArgumentException('Invalid file status.');
|
||||
}
|
||||
}
|
||||
|
||||
private function ensureFolder(string $path): int
|
||||
{
|
||||
$segments = array_values(array_filter(explode('/', str_replace('\\', '/', $path)), 'strlen'));
|
||||
$segments = array_values(array_filter($segments, static fn (string $part): bool => $part !== '.'));
|
||||
if (in_array('..', $segments, true)) {
|
||||
throw new InvalidArgumentException('Folder traversal is not allowed.');
|
||||
}
|
||||
$parent = null;
|
||||
$current = '';
|
||||
foreach (array_merge([''], $segments) as $segment) {
|
||||
$current = $segment === '' ? '/' : rtrim($current, '/') . '/' . $segment;
|
||||
$this->execute(
|
||||
'INSERT INTO folders (parent_id, name, path) VALUES (?, ?, ?) ON CONFLICT (path) DO NOTHING',
|
||||
[$parent, $segment === '' ? '/' : $segment, $current]
|
||||
);
|
||||
$parent = (int) $this->row('SELECT id FROM folders WHERE path = ?', [$current])['id'];
|
||||
}
|
||||
return $parent;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user