mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-10-30 18:30:34 +03:00
* keep base path in logout redirect * store manual event sub labels as verified objects Manual events created with a sub label were stored in the review item as "label: sub_label", so review previews and collapsed History cards showed a generic icon, the review label filter missed them, and the sub label never reached sub_labels for chips, notifications, or GenAI prompts. The object processor now publishes the label and sub label separately, and the review maintainer stores them as label-verified plus a sub_labels entry, the same as tracked objects. * only expire snapshots for events that still have one Snapshot expiry selected every event older than snapshot retention, including events whose snapshot was already deleted, so with snapshot retention shorter than alert/detection retention the same missing files were unlinked again every cleanup cycle until the event row expired. That was nearly free on local disk but caused constant metadata load on network shares. The expiry queries now filter on has_snapshot so each snapshot is deleted once.
88 lines
2.9 KiB
Python
88 lines
2.9 KiB
Python
"""Tests for snapshot expiration in event cleanup."""
|
|
|
|
import datetime
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from peewee import SqliteDatabase
|
|
|
|
from frigate.config import FrigateConfig
|
|
from frigate.events.cleanup import EventCleanup
|
|
from frigate.models import Event, Timeline
|
|
|
|
|
|
class TestEventCleanupSnapshots(unittest.TestCase):
|
|
def setUp(self):
|
|
self.db = SqliteDatabase(":memory:")
|
|
models = [Event, Timeline]
|
|
self.db.bind(models)
|
|
self.db.create_tables(models)
|
|
|
|
config = FrigateConfig(
|
|
**{
|
|
"mqtt": {"host": "mqtt"},
|
|
"snapshots": {"enabled": True, "retain": {"default": 14}},
|
|
"cameras": {
|
|
"front_door": {
|
|
"ffmpeg": {
|
|
"inputs": [
|
|
{
|
|
"path": "rtsp://10.0.0.1:554/video",
|
|
"roles": ["detect"],
|
|
},
|
|
]
|
|
},
|
|
}
|
|
},
|
|
}
|
|
)
|
|
self.cleanup = EventCleanup(config, MagicMock(), MagicMock())
|
|
|
|
def tearDown(self):
|
|
self.db.close()
|
|
|
|
def _insert_event(self, id: str, camera: str, has_snapshot: bool) -> None:
|
|
end_time = (datetime.datetime.now() - datetime.timedelta(days=20)).timestamp()
|
|
Event.create(
|
|
id=id,
|
|
label="person",
|
|
camera=camera,
|
|
start_time=end_time - 10,
|
|
end_time=end_time,
|
|
top_score=0.9,
|
|
score=0.9,
|
|
false_positive=False,
|
|
zones=[],
|
|
thumbnail="",
|
|
has_clip=True,
|
|
has_snapshot=has_snapshot,
|
|
region=[],
|
|
box=[],
|
|
area=0,
|
|
retain_indefinitely=False,
|
|
plus_id="",
|
|
model_hash="",
|
|
detector_type="cpu",
|
|
model_type="ssd",
|
|
data={},
|
|
)
|
|
|
|
@patch("frigate.events.cleanup.delete_event_snapshot", return_value=True)
|
|
def test_expired_snapshots_are_only_deleted_once(self, delete_snapshot):
|
|
# events stay in the db while their clip is retained, so events whose
|
|
# snapshot already expired must not be selected again
|
|
for camera in ("front_door", "removed_camera"):
|
|
self._insert_event(f"{camera}-pending", camera, has_snapshot=True)
|
|
self._insert_event(f"{camera}-expired", camera, has_snapshot=False)
|
|
|
|
self.cleanup.expire_snapshots()
|
|
|
|
deleted = sorted(c.args[0].id for c in delete_snapshot.call_args_list)
|
|
assert deleted == ["front_door-pending", "removed_camera-pending"]
|
|
assert not Event.select().where(Event.has_snapshot == True).exists()
|
|
|
|
delete_snapshot.reset_mock()
|
|
self.cleanup.expire_snapshots()
|
|
|
|
delete_snapshot.assert_not_called()
|