Initial upload: snippet-manager with CI/CD workflow
Some checks failed
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
2026-03-22 11:22:29 +00:00
parent 7a9ce5df26
commit 747bcf924e

80
snip/sync/discovery.py Normal file
View File

@@ -0,0 +1,80 @@
"""mDNS/Bonjour peer discovery for local network."""
import asyncio
import socket
from typing import Any
from zeroconf import ServiceInfo, Zeroconf
class DiscoveryService:
SERVICE_TYPE = "_snippets._tcp.local."
SERVICE_NAME = "snip"
def __init__(self, port: int = 8765):
self.port = port
self.zeroconf = None
self.service_info = None
def register(self, peer_id: str, host: str | None = None):
"""Register this peer on the network."""
if host is None:
host = socket.gethostbyname(socket.gethostname())
self.zeroconf = Zeroconf()
self.service_info = ServiceInfo(
self.SERVICE_TYPE,
f"{self.SERVICE_NAME}_{peer_id}.{self.SERVICE_TYPE}",
addresses=[socket.inet_aton(host)],
port=self.port,
properties={"peer_id": peer_id},
)
self.zeroconf.register_service(self.service_info)
def unregister(self):
"""Unregister this peer from the network."""
if self.zeroconf and self.service_info:
self.zeroconf.unregister_service(self.service_info)
self.zeroconf.close()
def discover_peers(self, timeout: float = 5.0) -> list[dict[str, Any]]:
"""Discover other peers on the network."""
peers = []
zeroconf = Zeroconf()
try:
for info in zeroconf.cache.entries_with_type(self.SERVICE_TYPE):
if isinstance(info, list):
for item in info:
if hasattr(item, "addresses"):
for addr in item.addresses:
peer_host = socket.inet_ntoa(addr)
peer_id = item.properties.get(b"peer_id", b"").decode()
peers.append({
"peer_id": peer_id,
"host": peer_host,
"port": item.port,
})
except Exception:
pass
finally:
zeroconf.close()
return peers
def discover_peers_async(self, timeout: float = 5.0) -> list[dict[str, Any]]:
"""Async version of peer discovery."""
return asyncio.run(self._discover_async(timeout))
async def _discover_async(self, timeout: float) -> list[dict[str, Any]]:
peers = []
zeroconf = Zeroconf()
try:
await asyncio.sleep(timeout)
except Exception:
pass
finally:
zeroconf.close()
return peers