mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-07 14:58:56 +00:00
Merge pull request #643 from PR0M3TH3AN/codex/implement-observer-pattern-in-pubsub-module
Add event bus and sync progress events
This commit is contained in:
@@ -15,6 +15,7 @@ import json
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from .manager import PasswordManager
|
from .manager import PasswordManager
|
||||||
|
from .pubsub import bus
|
||||||
|
|
||||||
|
|
||||||
class VaultExportRequest(BaseModel):
|
class VaultExportRequest(BaseModel):
|
||||||
@@ -202,7 +203,9 @@ class SyncService:
|
|||||||
"""Publish the vault to Nostr and return event info."""
|
"""Publish the vault to Nostr and return event info."""
|
||||||
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
|
bus.publish("sync_started")
|
||||||
result = self._manager.sync_vault()
|
result = self._manager.sync_vault()
|
||||||
|
bus.publish("sync_finished", result)
|
||||||
if not result:
|
if not result:
|
||||||
return None
|
return None
|
||||||
return SyncResponse(**result)
|
return SyncResponse(**result)
|
||||||
|
@@ -33,6 +33,7 @@ from .vault import Vault
|
|||||||
from .portable_backup import export_backup, import_backup
|
from .portable_backup import export_backup, import_backup
|
||||||
from .totp import TotpManager
|
from .totp import TotpManager
|
||||||
from .entry_types import EntryType
|
from .entry_types import EntryType
|
||||||
|
from .pubsub import bus
|
||||||
from utils.key_derivation import (
|
from utils.key_derivation import (
|
||||||
derive_key_from_parent_seed,
|
derive_key_from_parent_seed,
|
||||||
derive_key_from_password,
|
derive_key_from_password,
|
||||||
@@ -1243,7 +1244,9 @@ class PasswordManager:
|
|||||||
|
|
||||||
def _worker() -> None:
|
def _worker() -> None:
|
||||||
try:
|
try:
|
||||||
asyncio.run(self.sync_vault_async(alt_summary=alt_summary))
|
bus.publish("sync_started")
|
||||||
|
result = asyncio.run(self.sync_vault_async(alt_summary=alt_summary))
|
||||||
|
bus.publish("sync_finished", result)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logging.error(f"Background vault sync failed: {exc}", exc_info=True)
|
logging.error(f"Background vault sync failed: {exc}", exc_info=True)
|
||||||
|
|
||||||
@@ -1252,7 +1255,13 @@ class PasswordManager:
|
|||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
threading.Thread(target=_worker, daemon=True).start()
|
threading.Thread(target=_worker, daemon=True).start()
|
||||||
else:
|
else:
|
||||||
asyncio.create_task(self.sync_vault_async(alt_summary=alt_summary))
|
|
||||||
|
async def _async_worker() -> None:
|
||||||
|
bus.publish("sync_started")
|
||||||
|
result = await self.sync_vault_async(alt_summary=alt_summary)
|
||||||
|
bus.publish("sync_finished", result)
|
||||||
|
|
||||||
|
asyncio.create_task(_async_worker())
|
||||||
|
|
||||||
async def attempt_initial_sync_async(self) -> bool:
|
async def attempt_initial_sync_async(self) -> bool:
|
||||||
"""Attempt to download the initial vault snapshot from Nostr.
|
"""Attempt to download the initial vault snapshot from Nostr.
|
||||||
|
27
src/seedpass/core/pubsub.py
Normal file
27
src/seedpass/core/pubsub.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
from typing import Callable, Dict, List, Any
|
||||||
|
|
||||||
|
|
||||||
|
class PubSub:
|
||||||
|
"""Simple in-process event bus using the observer pattern."""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._subscribers: Dict[str, List[Callable[..., None]]] = defaultdict(list)
|
||||||
|
|
||||||
|
def subscribe(self, event: str, callback: Callable[..., None]) -> None:
|
||||||
|
"""Register ``callback`` to be invoked when ``event`` is published."""
|
||||||
|
self._subscribers[event].append(callback)
|
||||||
|
|
||||||
|
def unsubscribe(self, event: str, callback: Callable[..., None]) -> None:
|
||||||
|
"""Unregister ``callback`` from ``event`` notifications."""
|
||||||
|
if callback in self._subscribers.get(event, []):
|
||||||
|
self._subscribers[event].remove(callback)
|
||||||
|
|
||||||
|
def publish(self, event: str, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Notify all subscribers of ``event`` passing ``*args`` and ``**kwargs``."""
|
||||||
|
for callback in list(self._subscribers.get(event, [])):
|
||||||
|
callback(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
# Global bus instance for convenience
|
||||||
|
bus = PubSub()
|
28
src/tests/test_pubsub.py
Normal file
28
src/tests/test_pubsub.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from seedpass.core.pubsub import PubSub
|
||||||
|
|
||||||
|
|
||||||
|
def test_subscribe_and_publish():
|
||||||
|
bus = PubSub()
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def handler(arg):
|
||||||
|
calls.append(arg)
|
||||||
|
|
||||||
|
bus.subscribe("event", handler)
|
||||||
|
bus.publish("event", 123)
|
||||||
|
|
||||||
|
assert calls == [123]
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsubscribe():
|
||||||
|
bus = PubSub()
|
||||||
|
calls = []
|
||||||
|
|
||||||
|
def handler():
|
||||||
|
calls.append(True)
|
||||||
|
|
||||||
|
bus.subscribe("event", handler)
|
||||||
|
bus.unsubscribe("event", handler)
|
||||||
|
bus.publish("event")
|
||||||
|
|
||||||
|
assert calls == []
|
Reference in New Issue
Block a user