Fix recordings storage roots to ignore pseudo stat paths

This commit is contained in:
ibs0d 2026-03-07 21:58:44 +11:00
parent ced95052ee
commit 51b66857a0
2 changed files with 74 additions and 4 deletions

View File

@ -37,8 +37,40 @@ router = APIRouter(tags=[Tags.recordings])
def get_recordings_storage_usage(request: Request):
storage_stats = request.app.stats_emitter.get_latest_stats()["service"]["storage"]
configured_recording_paths = request.app.frigate_config.get_recordings_paths()
recording_stats = [storage_stats.get(path, {}) for path in configured_recording_paths]
def normalize_storage_path(path: str) -> str:
return str(Path(path)).rstrip("/") or "/"
def resolve_root_storage_stats(root_path: str) -> dict:
normalized_root = normalize_storage_path(root_path)
direct_stats = storage_stats.get(normalized_root, {})
if direct_stats:
return direct_stats
# Some stats payloads include date/hour pseudo-paths under a configured
# recordings root. In that case, use a descendant stat for the owning root
# without exposing the pseudo-path as a separate root entry.
descendant_candidates = [
(normalize_storage_path(path), stats)
for path, stats in storage_stats.items()
if normalize_storage_path(path).startswith(f"{normalized_root}/") and stats
]
if not descendant_candidates:
return {}
descendant_candidates.sort(key=lambda item: len(item[0]))
return descendant_candidates[0][1]
configured_recording_paths = sorted(
{
normalize_storage_path(path)
for path in request.app.frigate_config.get_recordings_paths()
}
)
recording_stats = [
resolve_root_storage_stats(path) for path in configured_recording_paths
]
total_mb = sum(stat.get("total", 0) for stat in recording_stats)
if total_mb == 0:
@ -57,11 +89,13 @@ def get_recordings_storage_usage(request: Request):
recording_roots = []
all_recording_roots = sorted(
set(configured_recording_paths).union(root_camera_usages.keys())
set(configured_recording_paths).union(
normalize_storage_path(path) for path in root_camera_usages.keys()
)
)
for root_path in all_recording_roots:
root_stats = storage_stats.get(root_path, {})
root_stats = resolve_root_storage_stats(root_path)
total = root_stats.get("total", 0)
used = root_stats.get("used", 0)
free = root_stats.get("free", 0)

View File

@ -307,3 +307,39 @@ class TestHttpRecordingsStorage(BaseTestHttp):
assert "/video1/2026-03-07/08" not in roots
assert "/video1/2026-03-07/09" not in roots
def test_recordings_storage_ignores_pseudo_root_storage_stat_entries(self):
self.minimal_config["cameras"]["back_yard"] = {
"ffmpeg": {
"inputs": [{"path": "rtsp://10.0.0.2:554/video", "roles": ["detect"]}]
},
"detect": {"height": 1080, "width": 1920, "fps": 5},
"path": "/video1",
}
self.test_stats["service"]["storage"]["/video1"] = {
"free": 600,
"mount_type": "ext4",
"total": 1000,
"used": 400,
}
self.test_stats["service"]["storage"]["/media/frigate/recordings/2026-03-07/10"] = {
"free": 700,
"mount_type": "ext4",
"total": 1000,
"used": 300,
}
self.test_stats["service"]["storage"]["/video1/2026-03-07/10"] = {
"free": 500,
"mount_type": "ext4",
"total": 1000,
"used": 500,
}
app = self._build_app()
with AuthTestClient(app) as client:
payload = client.get("/recordings/storage").json()
root_paths = {root["path"] for root in payload["recording_roots"]}
assert root_paths == {"/media/frigate/recordings", "/video1"}