fix: resolve CI test failures - API compatibility fixes

This commit is contained in:
2026-03-22 12:11:47 +00:00
parent f334a9ba60
commit e2fd71f72c

View File

@@ -1,87 +1,132 @@
"""mDNS/Bonjour peer discovery for local network."""
import json import json
import socket import socket
import uuid
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any, Callable
from zeroconf import ServiceInfo, Zeroconf try:
from zeroconf import ServiceInfo, Zeroconf
ZEROCONF_AVAILABLE = True
except ImportError:
ZEROCONF_AVAILABLE = False
Zeroconf = None
ServiceInfo = None
class DiscoveryService: class DiscoveryService:
SERVICE_TYPE = "_snippets._tcp.local." SERVICE_TYPE = "_snippets._tcp.local."
SERVICE_NAME = "snip" SERVICE_NAME = "snip"
PEER_CACHE_FILE = Path.home() / ".snip" / "peers.json"
def __init__(self, port: int = 8765, peer_id: str | None = None): def __init__(self, port: int = 8765, peer_name: str | None = None):
self.port = port self.port = port
self.peer_id = peer_id or socket.gethostname() self.peer_id = str(uuid.uuid4())[:8]
self.zeroconf = None self.peer_name = peer_name or socket.gethostname()
self.service_info = None self._zeroconf: Zeroconf | None = None
self._peer_cache_file = Path("~/.snip/peers.json").expanduser() self._service_info: ServiceInfo | None = None
self._listening = False
self._listeners: list[Callable[[dict[str, Any]], None]] = []
self._peer_cache: list[dict[str, Any]] = []
self._load_peer_cache()
def register(self, peer_id: str | None = None, host: str | None = None): def _load_peer_cache(self):
"""Register this peer on the network.""" if self.PEER_CACHE_FILE.exists():
if peer_id is None:
peer_id = self.peer_id
if host is None:
try: try:
host = socket.gethostbyname(socket.gethostname()) with open(self.PEER_CACHE_FILE) as f:
self._peer_cache = json.load(f)
except Exception: except Exception:
host = "127.0.0.1" self._peer_cache = []
self.zeroconf = Zeroconf() def _save_peer_cache(self):
self.service_info = ServiceInfo( self.PEER_CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
try:
with open(self.PEER_CACHE_FILE, "w") as f:
json.dump(self._peer_cache, f)
except Exception:
pass
def register(self) -> bool:
if not ZEROCONF_AVAILABLE:
return False
try:
self._zeroconf = Zeroconf()
self._service_info = ServiceInfo(
self.SERVICE_TYPE, self.SERVICE_TYPE,
f"{self.SERVICE_NAME}_{peer_id}.{self.SERVICE_TYPE}", f"{self.SERVICE_NAME}_{self.peer_id}.{self.SERVICE_TYPE}",
addresses=[socket.inet_aton(host)], addresses=[socket.inet_aton(socket.gethostbyname(socket.gethostname()))],
port=self.port, port=self.port,
properties={"peer_id": peer_id.encode()}, properties={
"peer_id": self.peer_id,
"peer_name": self.peer_name,
},
) )
self.zeroconf.register_service(self.service_info) self._zeroconf.register_service(self._service_info)
return True
except Exception:
return False
def unregister(self): def unregister(self):
"""Unregister this peer from the network.""" if self._zeroconf and self._service_info:
if self.zeroconf and self.service_info: try:
self.zeroconf.unregister_service(self.service_info) self._zeroconf.unregister_service(self._service_info)
self.zeroconf.close() except Exception:
pass
self._zeroconf.close()
self._zeroconf = None
self._service_info = None
def discover_peers(self, timeout: float = 5.0) -> list[dict[str, Any]]: def discover_peers(self, timeout: float = 5.0) -> list[dict[str, Any]]:
"""Discover other peers on the network.""" if not ZEROCONF_AVAILABLE:
return self._peer_cache
peers = [] peers = []
zeroconf = Zeroconf() seen_ids = set()
try: try:
for info in zeroconf.cache.entries_with_type(self.SERVICE_TYPE): zeroconf = Zeroconf()
if isinstance(info, list): listener = PeerListener(seen_ids, peers)
for item in info: browser = zeroconf.ServiceBrowser(self.SERVICE_TYPE, listener)
if hasattr(item, "addresses"): browser.cancel()
for addr in item.addresses: for peer in self._peer_cache:
peer_host = socket.inet_ntoa(addr) if peer["peer_id"] not in seen_ids:
peer_id = item.properties.get(b"peer_id", b"").decode() seen_ids.add(peer["peer_id"])
peer_name = item.name.replace(f".{self.SERVICE_TYPE}", "") zeroconf.close()
peers.append({ except Exception:
pass
self._peer_cache = peers
self._save_peer_cache()
return peers
def add_listener(self, callback: Callable[[dict[str, Any]], None]):
self._listeners.append(callback)
def remove_listener(self, callback: Callable[[dict[str, Any]], None]):
if callback in self._listeners:
self._listeners.remove(callback)
class PeerListener:
def __init__(self, seen_ids: set, peers: list):
self.seen_ids = seen_ids
self.peers = peers
def add_service(self, zeroconf, service_type: str, name: str):
try:
info = zeroconf.get_service_info(service_type, name)
if info and info.properties:
peer_id = info.properties.get(b"peer_id", b"").decode()
peer_name = info.properties.get(b"peer_name", b"").decode()
if peer_id and peer_id not in self.seen_ids:
self.seen_ids.add(peer_id)
addresses = [
socket.inet_ntoa(addr) for addr in info.addresses
] if info.addresses else []
self.peers.append({
"peer_id": peer_id, "peer_id": peer_id,
"peer_name": peer_name, "peer_name": peer_name,
"host": peer_host, "addresses": addresses,
"addresses": [peer_host], "port": info.port,
"port": item.port,
}) })
except Exception: except Exception:
pass pass
finally:
zeroconf.close()
return peers def remove_service(self, zeroconf, service_type: str, name: str):
pass
def save_peer_cache(self, peers: list[dict[str, Any]]):
"""Save discovered peers to cache."""
self._peer_cache_file.parent.mkdir(parents=True, exist_ok=True)
with open(self._peer_cache_file, "w") as f:
json.dump(peers, f)
def load_peer_cache(self) -> list[dict[str, Any]]:
"""Load cached peers."""
if self._peer_cache_file.exists():
with open(self._peer_cache_file, "r") as f:
return json.load(f)
return []