feat(plugins): isolate device integrations
This commit is contained in:
@@ -14,8 +14,6 @@ from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, TypeGuard
|
||||
|
||||
from k1link.viewer.replay import MAX_METADATA_LINE_CHARS
|
||||
|
||||
from .models import RecordedMediaArtifact, ReplayCommand, SessionIntegrityError
|
||||
|
||||
CAMERA_ARCHIVE_SCHEMA = "missioncore.camera-recording/v1"
|
||||
@@ -139,7 +137,7 @@ class RecordedMediaInspector:
|
||||
cached = self._cache.get(key)
|
||||
if cached is not None:
|
||||
identity = _prepared_source_identity(
|
||||
replay.source_path,
|
||||
replay,
|
||||
epoch_paths,
|
||||
cached.manifest.epochs,
|
||||
)
|
||||
@@ -151,15 +149,14 @@ class RecordedMediaInspector:
|
||||
with self._lock:
|
||||
self._cache[key] = _CachedManifest(identity=identity, manifest=manifest)
|
||||
return manifest
|
||||
origin_epoch_ns, origin_monotonic_ns = _raw_timeline_origin(replay.source_path)
|
||||
manifest = _read_manifest(
|
||||
artifact,
|
||||
epoch_paths,
|
||||
origin_epoch_ns=origin_epoch_ns,
|
||||
origin_monotonic_ns=origin_monotonic_ns,
|
||||
origin_epoch_ns=replay.timeline_origin_epoch_ns,
|
||||
origin_monotonic_ns=replay.timeline_origin_monotonic_ns,
|
||||
)
|
||||
identity = _prepared_source_identity(
|
||||
replay.source_path,
|
||||
replay,
|
||||
epoch_paths,
|
||||
manifest.epochs,
|
||||
)
|
||||
@@ -186,7 +183,7 @@ class RecordedMediaInspector:
|
||||
document = _decode_prepared_sidecar(payload)
|
||||
manifest = _manifest_from_sidecar(document, artifact, epoch_paths)
|
||||
identity = _prepared_source_identity(
|
||||
replay.source_path,
|
||||
replay,
|
||||
epoch_paths,
|
||||
manifest.epochs,
|
||||
)
|
||||
@@ -631,15 +628,15 @@ def _epoch_paths(source_path: Path) -> tuple[Path, ...]:
|
||||
|
||||
|
||||
def _prepared_source_identity(
|
||||
raw_path: Path,
|
||||
replay: ReplayCommand,
|
||||
epoch_paths: tuple[Path, ...],
|
||||
epochs: tuple[RecordedMediaEpoch, ...],
|
||||
) -> tuple[tuple[int, int, int, int], ...]:
|
||||
if len(epoch_paths) != len(epochs):
|
||||
raise SessionIntegrityError("recorded media epoch identity is inconsistent")
|
||||
identity: list[tuple[int, int, int, int]] = []
|
||||
for source_file in (raw_path, raw_path.with_name("mqtt.metadata.jsonl")):
|
||||
metadata = _confined_file_stat(source_file, raw_path.parent)
|
||||
for artifact in replay.artifacts:
|
||||
metadata = _session_artifact_stat(artifact.path, replay.session_root)
|
||||
identity.append(
|
||||
(metadata.st_dev, metadata.st_ino, metadata.st_size, metadata.st_mtime_ns)
|
||||
)
|
||||
@@ -671,32 +668,15 @@ def _prepared_source_identity(
|
||||
return tuple(identity)
|
||||
|
||||
|
||||
def _raw_timeline_origin(raw_path: Path) -> tuple[int, int]:
|
||||
metadata_path = raw_path.with_name("mqtt.metadata.jsonl")
|
||||
def _session_artifact_stat(path: Path, session_root: Path) -> os.stat_result:
|
||||
try:
|
||||
line = _read_first_confined_line(
|
||||
metadata_path,
|
||||
raw_path.parent,
|
||||
MAX_METADATA_LINE_CHARS,
|
||||
).decode("utf-8")
|
||||
except (OSError, UnicodeDecodeError) as exc:
|
||||
raise SessionIntegrityError("native capture timing metadata is unavailable") from exc
|
||||
if not line or len(line) > MAX_METADATA_LINE_CHARS or not line.endswith(("\n", "\r")):
|
||||
raise SessionIntegrityError("native capture timing origin is incomplete")
|
||||
try:
|
||||
record = json.loads(line)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise SessionIntegrityError("native capture timing origin is invalid") from exc
|
||||
epoch_ns = record.get("received_at_epoch_ns") if isinstance(record, dict) else None
|
||||
monotonic_ns = record.get("received_monotonic_ns") if isinstance(record, dict) else None
|
||||
if (
|
||||
record.get("record_type") != "message"
|
||||
or record.get("sequence") != 1
|
||||
or not _non_negative_int(epoch_ns)
|
||||
or not _non_negative_int(monotonic_ns)
|
||||
):
|
||||
raise SessionIntegrityError("native capture timing origin is invalid")
|
||||
return int(epoch_ns), int(monotonic_ns)
|
||||
session = session_root.resolve(strict=True)
|
||||
parent = path.parent.resolve(strict=True)
|
||||
except OSError as exc:
|
||||
raise SessionIntegrityError("recording source artifact is missing") from exc
|
||||
if not parent.is_relative_to(session):
|
||||
raise SessionIntegrityError("recording source artifact escapes its session")
|
||||
return _confined_file_stat(path, parent)
|
||||
|
||||
|
||||
def _read_manifest(
|
||||
|
||||
Reference in New Issue
Block a user