generated from Nodarx/template
Initial service DAV module
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KTXM\ServiceDav;
|
||||
|
||||
use DI\Attribute\Inject;
|
||||
|
||||
/**
|
||||
* Bidirectional mapping between DAV object URIs (chosen by clients) and entity
|
||||
* identifiers assigned by the provider stores.
|
||||
*
|
||||
* Stored as a pair of JSON files per logical "map" (keyed by a mapId that
|
||||
* encodes tenant / user / calendar-or-addressbook context). File locking is
|
||||
* used to guard against concurrent modifications.
|
||||
*
|
||||
* This class is intentionally simple: it is a best-effort persistence layer for
|
||||
* single-server deployments. A database-backed implementation should be
|
||||
* substituted for high-concurrency or multi-node environments.
|
||||
*/
|
||||
class DavUriMap
|
||||
{
|
||||
private string $dir;
|
||||
|
||||
public function __construct(#[Inject('rootDir')] string $rootDir)
|
||||
{
|
||||
$this->dir = $rootDir . '/var/dav';
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public API
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Look up the provider entity identifier for a given DAV object URI.
|
||||
*
|
||||
* @param string $mapId Logical map bucket (e.g. "{tenant}:{user}:cal:{calId}")
|
||||
* @param string $objectUri DAV leaf URI (e.g. "abc-123.ics")
|
||||
*/
|
||||
public function getEntityId(string $mapId, string $objectUri): ?string
|
||||
{
|
||||
return $this->read($mapId)['obj_to_ent'][$objectUri] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the DAV object URI for a given provider entity identifier.
|
||||
*/
|
||||
public function getObjectUri(string $mapId, string $entityId): ?string
|
||||
{
|
||||
return $this->read($mapId)['ent_to_obj'][(string) $entityId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a mapping from a DAV object URI to a provider entity identifier.
|
||||
*/
|
||||
public function set(string $mapId, string $objectUri, string|int $entityId): void
|
||||
{
|
||||
$entityId = (string) $entityId;
|
||||
$this->update($mapId, static function (array $data) use ($objectUri, $entityId): array {
|
||||
$data['obj_to_ent'][$objectUri] = $entityId;
|
||||
$data['ent_to_obj'][$entityId] = $objectUri;
|
||||
return $data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the mapping for a DAV object URI.
|
||||
*/
|
||||
public function delete(string $mapId, string $objectUri): void
|
||||
{
|
||||
$this->update($mapId, static function (array $data) use ($objectUri): array {
|
||||
$entityId = $data['obj_to_ent'][$objectUri] ?? null;
|
||||
if ($entityId !== null) {
|
||||
unset($data['ent_to_obj'][$entityId]);
|
||||
}
|
||||
unset($data['obj_to_ent'][$objectUri]);
|
||||
return $data;
|
||||
});
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Internal helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private function mapFile(string $mapId): string
|
||||
{
|
||||
return $this->dir . '/map_' . md5($mapId) . '.json';
|
||||
}
|
||||
|
||||
private function read(string $mapId): array
|
||||
{
|
||||
$file = $this->mapFile($mapId);
|
||||
if (!file_exists($file)) {
|
||||
return ['obj_to_ent' => [], 'ent_to_obj' => []];
|
||||
}
|
||||
$contents = file_get_contents($file);
|
||||
if ($contents === false) {
|
||||
return ['obj_to_ent' => [], 'ent_to_obj' => []];
|
||||
}
|
||||
return json_decode($contents, true) ?? ['obj_to_ent' => [], 'ent_to_obj' => []];
|
||||
}
|
||||
|
||||
private function update(string $mapId, callable $updater): void
|
||||
{
|
||||
if (!is_dir($this->dir)) {
|
||||
mkdir($this->dir, 0750, true);
|
||||
}
|
||||
|
||||
$file = $this->mapFile($mapId);
|
||||
$fh = fopen($file, 'c+');
|
||||
if ($fh === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
flock($fh, LOCK_EX);
|
||||
$contents = stream_get_contents($fh);
|
||||
$data = json_decode($contents ?: '{}', true)
|
||||
?? ['obj_to_ent' => [], 'ent_to_obj' => []];
|
||||
if (!isset($data['obj_to_ent'])) {
|
||||
$data['obj_to_ent'] = [];
|
||||
}
|
||||
if (!isset($data['ent_to_obj'])) {
|
||||
$data['ent_to_obj'] = [];
|
||||
}
|
||||
$data = $updater($data);
|
||||
ftruncate($fh, 0);
|
||||
rewind($fh);
|
||||
fwrite($fh, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||||
} finally {
|
||||
flock($fh, LOCK_UN);
|
||||
fclose($fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user