mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-08-06 17:03:32 +02:00
Modify CompanionBridge integration to support persisting NodePrefs
- Added new methods to SQLiteHandler for loading and saving companion preferences as JSON, improving data persistence. - Introduced a migration to create a companion_prefs table for storing preferences, ensuring compatibility with existing data. - Refactored main.py to utilize RepeaterCompanionBridge instead of CompanionBridge, aligning with the new architecture.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
Exposes the MeshCore companion frame protocol over TCP for standard clients.
|
||||
"""
|
||||
|
||||
from .bridge import RepeaterCompanionBridge
|
||||
from .constants import (
|
||||
CMD_APP_START,
|
||||
CMD_GET_CONTACTS,
|
||||
@@ -17,6 +18,7 @@ from .frame_server import CompanionFrameServer
|
||||
|
||||
__all__ = [
|
||||
"CompanionFrameServer",
|
||||
"RepeaterCompanionBridge",
|
||||
"CMD_APP_START",
|
||||
"CMD_GET_CONTACTS",
|
||||
"CMD_SEND_TXT_MSG",
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Repeater CompanionBridge with SQLite-backed preference persistence.
|
||||
|
||||
Persists full NodePrefs as a JSON blob so companion settings (including
|
||||
auto-add config) survive repeater restarts. Merge-on-load supports
|
||||
schema evolution when NodePrefs gains or loses fields.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import logging
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
from pymc_core.companion import CompanionBridge
|
||||
|
||||
logger = logging.getLogger("RepeaterCompanionBridge")
|
||||
|
||||
|
||||
class RepeaterCompanionBridge(CompanionBridge):
|
||||
"""CompanionBridge that persists and loads prefs (full NodePrefs) via SQLite JSON blob."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
identity,
|
||||
packet_injector: Callable[..., Any],
|
||||
node_name: str = "pyMC",
|
||||
adv_type: int = 1,
|
||||
max_contacts: int = 1000,
|
||||
max_channels: int = 40,
|
||||
offline_queue_size: int = 512,
|
||||
radio_config: Optional[dict] = None,
|
||||
authenticate_callback: Optional[Callable[..., tuple[bool, int]]] = None,
|
||||
initial_contacts: Optional[Any] = None,
|
||||
*,
|
||||
sqlite_handler=None,
|
||||
companion_hash: str = "",
|
||||
) -> None:
|
||||
self._sqlite_handler = sqlite_handler
|
||||
self._companion_hash = companion_hash
|
||||
super().__init__(
|
||||
identity=identity,
|
||||
packet_injector=packet_injector,
|
||||
node_name=node_name,
|
||||
adv_type=adv_type,
|
||||
max_contacts=max_contacts,
|
||||
max_channels=max_channels,
|
||||
offline_queue_size=offline_queue_size,
|
||||
radio_config=radio_config,
|
||||
authenticate_callback=authenticate_callback,
|
||||
initial_contacts=initial_contacts,
|
||||
)
|
||||
|
||||
def _save_prefs(self) -> None:
|
||||
"""Persist full NodePrefs as JSON to SQLite."""
|
||||
if not self._sqlite_handler or not self._companion_hash:
|
||||
return
|
||||
try:
|
||||
prefs_dict = dataclasses.asdict(self.prefs)
|
||||
self._sqlite_handler.companion_save_prefs(self._companion_hash, prefs_dict)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to persist companion prefs: %s", e)
|
||||
|
||||
def _load_prefs(self) -> None:
|
||||
"""Load prefs from SQLite JSON and merge into self.prefs (only known keys)."""
|
||||
if not self._sqlite_handler or not self._companion_hash:
|
||||
return
|
||||
try:
|
||||
stored = self._sqlite_handler.companion_load_prefs(self._companion_hash)
|
||||
if not stored or not isinstance(stored, dict):
|
||||
return
|
||||
for key, value in stored.items():
|
||||
if not hasattr(self.prefs, key):
|
||||
continue
|
||||
current = getattr(self.prefs, key)
|
||||
try:
|
||||
if value is None:
|
||||
continue
|
||||
if isinstance(current, bool):
|
||||
setattr(self.prefs, key, bool(value))
|
||||
elif isinstance(current, int):
|
||||
setattr(self.prefs, key, int(value))
|
||||
elif isinstance(current, float):
|
||||
setattr(self.prefs, key, float(value))
|
||||
elif isinstance(current, str):
|
||||
setattr(self.prefs, key, str(value))
|
||||
else:
|
||||
setattr(self.prefs, key, value)
|
||||
except (TypeError, ValueError) as e:
|
||||
logger.debug("Skip prefs key %r: %s", key, e)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to load companion prefs: %s", e)
|
||||
@@ -423,6 +423,33 @@ class SQLiteHandler:
|
||||
)
|
||||
logger.info(f"Migration '{migration_name}' applied successfully")
|
||||
|
||||
# Migration 7: Add companion_prefs table (JSON blob for full NodePrefs persistence)
|
||||
migration_name = "add_companion_prefs"
|
||||
existing = conn.execute(
|
||||
"SELECT migration_name FROM migrations WHERE migration_name = ?",
|
||||
(migration_name,),
|
||||
).fetchone()
|
||||
|
||||
if not existing:
|
||||
cursor = conn.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='companion_prefs'"
|
||||
)
|
||||
if not cursor.fetchone():
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE companion_prefs (
|
||||
companion_hash TEXT PRIMARY KEY,
|
||||
prefs_json TEXT NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
logger.info("Created companion_prefs table")
|
||||
conn.execute(
|
||||
"INSERT INTO migrations (migration_name, applied_at) VALUES (?, ?)",
|
||||
(migration_name, time.time()),
|
||||
)
|
||||
logger.info(f"Migration '{migration_name}' applied successfully")
|
||||
|
||||
conn.commit()
|
||||
|
||||
except Exception as e:
|
||||
@@ -1786,6 +1813,41 @@ class SQLiteHandler:
|
||||
logger.error(f"Failed to upsert companion contact: {e}")
|
||||
return False
|
||||
|
||||
def companion_load_prefs(self, companion_hash: str) -> Optional[Dict]:
|
||||
"""Load persisted prefs for a companion. Returns parsed JSON dict or None if no row."""
|
||||
try:
|
||||
with sqlite3.connect(self.sqlite_path) as conn:
|
||||
cursor = conn.execute(
|
||||
"SELECT prefs_json FROM companion_prefs WHERE companion_hash = ?",
|
||||
(companion_hash,),
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
return json.loads(row[0])
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load companion prefs: {e}")
|
||||
return None
|
||||
|
||||
def companion_save_prefs(self, companion_hash: str, prefs: Dict) -> bool:
|
||||
"""Persist prefs for a companion as JSON. Upserts by companion_hash."""
|
||||
try:
|
||||
prefs_json = json.dumps(prefs)
|
||||
with sqlite3.connect(self.sqlite_path) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT INTO companion_prefs (companion_hash, prefs_json)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(companion_hash) DO UPDATE SET prefs_json = excluded.prefs_json
|
||||
""",
|
||||
(companion_hash, prefs_json),
|
||||
)
|
||||
conn.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save companion prefs: {e}")
|
||||
return False
|
||||
|
||||
def companion_load_channels(self, companion_hash: str) -> List[Dict]:
|
||||
"""Load channels for a companion from storage."""
|
||||
try:
|
||||
|
||||
+4
-3
@@ -356,10 +356,9 @@ class RepeaterDaemon:
|
||||
async def _load_companion_identities(self) -> None:
|
||||
"""Load companion identities from config and create CompanionBridge + frame server for each."""
|
||||
from pymc_core import LocalIdentity
|
||||
from pymc_core.companion import CompanionBridge
|
||||
from pymc_core.companion.models import Channel, Contact
|
||||
|
||||
from repeater.companion import CompanionFrameServer
|
||||
from repeater.companion import CompanionFrameServer, RepeaterCompanionBridge
|
||||
|
||||
companions_config = self.config.get("identities", {}).get("companions") or []
|
||||
if not companions_config:
|
||||
@@ -412,11 +411,13 @@ class RepeaterDaemon:
|
||||
tcp_port = settings.get("tcp_port", 5000)
|
||||
bind_address = settings.get("bind_address", "0.0.0.0")
|
||||
|
||||
bridge = CompanionBridge(
|
||||
bridge = RepeaterCompanionBridge(
|
||||
identity=identity,
|
||||
packet_injector=self.router.inject_packet,
|
||||
node_name=node_name,
|
||||
radio_config=radio_config,
|
||||
sqlite_handler=sqlite_handler,
|
||||
companion_hash=companion_hash_str,
|
||||
)
|
||||
|
||||
# Load contacts from SQLite
|
||||
|
||||
Reference in New Issue
Block a user