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); } } }