Files
MeshTerm/meshtools/core/monitor_store.py
T
Jean-Pierre MartineauandClaude Opus 4.8 1036ff52f6 Make passive monitor a non-blocking background toggle with live counts
The monitor was a blocking listen window that monopolized the app. Convert
it to an always-on background subscription that logs overheard adverts/
telemetry while the user keeps using the menu.

- Device: add subscribe_observations() as the non-blocking capture primitive;
  listen() is now a thin foreground wrapper over it. Mock streams a live
  packet burst + cadence instead of a bounded synth loop.
- MonitorService (ctx.monitor): owns the subscription, session/total counters,
  and the on/off preference; logs each packet as it arrives.
- MonitorStore: persist on/off in <config_dir>/monitor.json, default off,
  remembered between sessions and auto-resumed at launch.
- Menu: the monitor entry is now a toggle/heard-nodes control panel; the
  header shows live session/total counts and repaints ~1s via refresh_interval.
- CLI monitor gains --on/--off (preference) and keeps --seconds for a scripted
  foreground capture.
- Add Repository.observation_count(); README + tests (129 pass).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 13:35:43 -04:00

55 lines
2.1 KiB
Python

"""Persistence for the passive-monitor on/off preference.
Whether background monitoring is enabled is remembered between sessions in a small JSON
file in the config directory (``<config_dir>/monitor.json``), following the same pattern
as :class:`~meshtools.core.device_store.DeviceStore`: it is machine/user state managed by
the app rather than user-authored config, and it must survive a per-invocation ``--db``
override, so it lives beside the config rather than in the swappable database.
The preference defaults to *off*: a fresh install never captures until the user opts in.
"""
from __future__ import annotations
import json
from pathlib import Path
class MonitorStore:
"""Reads and writes the remembered passive-monitor on/off preference."""
def __init__(self, path: Path) -> None:
"""Open the store against a JSON file location.
Args:
path: Path to the JSON state file (created lazily on first write).
"""
self._path = path
def load_enabled(self) -> bool:
"""Return whether monitoring is enabled, defaulting to ``False``.
A missing or corrupt file is treated as "disabled" rather than an error, so a
stray edit never blocks startup and never silently starts capturing.
Returns:
``True`` if monitoring was left enabled, otherwise ``False``.
"""
try:
data = json.loads(self._path.read_text(encoding="utf-8"))
except (OSError, ValueError):
return False
return bool(data.get("enabled", False)) if isinstance(data, dict) else False
def save_enabled(self, enabled: bool) -> None:
"""Persist the monitoring on/off preference.
Args:
enabled: Whether background monitoring should be on for future sessions.
"""
self._path.parent.mkdir(parents=True, exist_ok=True)
# Atomic-ish replace so a crash mid-write can't leave a truncated file.
tmp = self._path.with_suffix(self._path.suffix + ".tmp")
tmp.write_text(json.dumps({"enabled": bool(enabled)}, indent=2), encoding="utf-8")
tmp.replace(self._path)