mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
Add StatsManager for single display
This commit is contained in:
15
src/main.py
15
src/main.py
@@ -281,6 +281,7 @@ def _display_live_stats(
|
|||||||
displayed if newer data exists on Nostr.
|
displayed if newer data exists on Nostr.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
stats_mgr = getattr(password_manager, "stats_manager", None)
|
||||||
display_fn = getattr(password_manager, "display_stats", None)
|
display_fn = getattr(password_manager, "display_stats", None)
|
||||||
sync_fn = getattr(password_manager, "start_background_sync", None)
|
sync_fn = getattr(password_manager, "start_background_sync", None)
|
||||||
if not callable(display_fn):
|
if not callable(display_fn):
|
||||||
@@ -294,12 +295,17 @@ def _display_live_stats(
|
|||||||
|
|
||||||
if not sys.stdin or not sys.stdin.isatty():
|
if not sys.stdin or not sys.stdin.isatty():
|
||||||
clear_screen()
|
clear_screen()
|
||||||
display_fn()
|
if stats_mgr is not None:
|
||||||
|
stats_mgr.display_stats_once(password_manager)
|
||||||
|
else:
|
||||||
|
display_fn()
|
||||||
note = get_notification_text(password_manager)
|
note = get_notification_text(password_manager)
|
||||||
if note:
|
if note:
|
||||||
print(note)
|
print(note)
|
||||||
print(colored("Press Enter to continue.", "cyan"))
|
print(colored("Press Enter to continue.", "cyan"))
|
||||||
pause()
|
pause()
|
||||||
|
if stats_mgr is not None:
|
||||||
|
stats_mgr.reset()
|
||||||
return
|
return
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -309,7 +315,10 @@ def _display_live_stats(
|
|||||||
except Exception: # pragma: no cover - sync best effort
|
except Exception: # pragma: no cover - sync best effort
|
||||||
logging.debug("Background sync failed during stats display")
|
logging.debug("Background sync failed during stats display")
|
||||||
clear_screen()
|
clear_screen()
|
||||||
display_fn()
|
if stats_mgr is not None:
|
||||||
|
stats_mgr.display_stats_once(password_manager)
|
||||||
|
else:
|
||||||
|
display_fn()
|
||||||
note = get_notification_text(password_manager)
|
note = get_notification_text(password_manager)
|
||||||
if note:
|
if note:
|
||||||
print(note)
|
print(note)
|
||||||
@@ -324,6 +333,8 @@ def _display_live_stats(
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print()
|
print()
|
||||||
break
|
break
|
||||||
|
if stats_mgr is not None:
|
||||||
|
stats_mgr.reset()
|
||||||
|
|
||||||
|
|
||||||
def handle_display_stats(password_manager: PasswordManager) -> None:
|
def handle_display_stats(password_manager: PasswordManager) -> None:
|
||||||
|
@@ -4,7 +4,14 @@
|
|||||||
|
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
__all__ = ["PasswordManager", "ConfigManager", "Vault", "EntryType", "StateManager"]
|
__all__ = [
|
||||||
|
"PasswordManager",
|
||||||
|
"ConfigManager",
|
||||||
|
"Vault",
|
||||||
|
"EntryType",
|
||||||
|
"StateManager",
|
||||||
|
"StatsManager",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name: str):
|
def __getattr__(name: str):
|
||||||
@@ -18,4 +25,6 @@ def __getattr__(name: str):
|
|||||||
return import_module(".entry_types", __name__).EntryType
|
return import_module(".entry_types", __name__).EntryType
|
||||||
if name == "StateManager":
|
if name == "StateManager":
|
||||||
return import_module(".state_manager", __name__).StateManager
|
return import_module(".state_manager", __name__).StateManager
|
||||||
|
if name == "StatsManager":
|
||||||
|
return import_module(".stats_manager", __name__).StatsManager
|
||||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||||
|
@@ -99,6 +99,7 @@ from utils.fingerprint_manager import FingerprintManager
|
|||||||
from nostr.client import NostrClient, DEFAULT_RELAYS, MANIFEST_ID_PREFIX
|
from nostr.client import NostrClient, DEFAULT_RELAYS, MANIFEST_ID_PREFIX
|
||||||
from .config_manager import ConfigManager
|
from .config_manager import ConfigManager
|
||||||
from .state_manager import StateManager
|
from .state_manager import StateManager
|
||||||
|
from .stats_manager import StatsManager
|
||||||
|
|
||||||
# Instantiate the logger
|
# Instantiate the logger
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -163,6 +164,7 @@ class PasswordManager:
|
|||||||
self.nostr_client: Optional[NostrClient] = None
|
self.nostr_client: Optional[NostrClient] = None
|
||||||
self.config_manager: Optional[ConfigManager] = None
|
self.config_manager: Optional[ConfigManager] = None
|
||||||
self.state_manager: Optional[StateManager] = None
|
self.state_manager: Optional[StateManager] = None
|
||||||
|
self.stats_manager: StatsManager = StatsManager()
|
||||||
self.notifications: queue.Queue[Notification] = queue.Queue()
|
self.notifications: queue.Queue[Notification] = queue.Queue()
|
||||||
self._current_notification: Optional[Notification] = None
|
self._current_notification: Optional[Notification] = None
|
||||||
self._notification_expiry: float = 0.0
|
self._notification_expiry: float = 0.0
|
||||||
|
20
src/seedpass/core/stats_manager.py
Normal file
20
src/seedpass/core/stats_manager.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
"""Manage display of stats screens."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
class StatsManager:
|
||||||
|
"""Track whether stats have been displayed."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._displayed = False
|
||||||
|
|
||||||
|
def display_stats_once(self, manager) -> None:
|
||||||
|
"""Display stats using ``manager`` once per reset."""
|
||||||
|
if not self._displayed:
|
||||||
|
manager.display_stats()
|
||||||
|
self._displayed = True
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""Reset the displayed flag."""
|
||||||
|
self._displayed = False
|
@@ -2,6 +2,7 @@ import sys
|
|||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import pytest
|
import pytest
|
||||||
|
from seedpass.core.stats_manager import StatsManager
|
||||||
|
|
||||||
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
sys.path.append(str(Path(__file__).resolve().parents[1]))
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ def _make_pm():
|
|||||||
return SimpleNamespace(
|
return SimpleNamespace(
|
||||||
display_stats=lambda: print("stats"),
|
display_stats=lambda: print("stats"),
|
||||||
start_background_sync=lambda: None,
|
start_background_sync=lambda: None,
|
||||||
|
stats_manager=StatsManager(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -57,3 +59,32 @@ def test_live_stats_triggers_background_sync(monkeypatch):
|
|||||||
main._display_live_stats(pm)
|
main._display_live_stats(pm)
|
||||||
|
|
||||||
assert called["sync"] >= 1
|
assert called["sync"] >= 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_stats_display_only_once(monkeypatch, capsys):
|
||||||
|
pm = _make_pm()
|
||||||
|
monkeypatch.setattr(main, "get_notification_text", lambda *_: "")
|
||||||
|
|
||||||
|
events = [TimeoutError(), KeyboardInterrupt()]
|
||||||
|
|
||||||
|
def fake_input(*_args, **_kwargs):
|
||||||
|
raise events.pop(0)
|
||||||
|
|
||||||
|
monkeypatch.setattr(main, "timed_input", fake_input)
|
||||||
|
main._display_live_stats(pm, interval=0.01)
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert out.count("stats") == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_stats_display_resets_after_exit(monkeypatch, capsys):
|
||||||
|
pm = _make_pm()
|
||||||
|
monkeypatch.setattr(main, "get_notification_text", lambda *_: "")
|
||||||
|
monkeypatch.setattr(
|
||||||
|
main,
|
||||||
|
"timed_input",
|
||||||
|
lambda *_args, **_kwargs: (_ for _ in ()).throw(KeyboardInterrupt()),
|
||||||
|
)
|
||||||
|
main._display_live_stats(pm)
|
||||||
|
main._display_live_stats(pm)
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert out.count("stats") == 2
|
||||||
|
Reference in New Issue
Block a user