Source code for mdns_beacon.cli.layouts
"""Console layout for mdns-beacon."""
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
from rich.console import RenderableType
from rich.live import Live
from rich.spinner import Spinner
from rich.table import Table
from rich.text import Text
from zeroconf import IPVersion, ServiceStateChange, Zeroconf
[docs]class BaseLayout(ABC):
"""Base cli layout.
Note:
Derived layouts must override the `renderable` property.
"""
def __init__(self, live: Live) -> None:
"""Init layout."""
self.live = live
self.live.update(self.renderable)
@property
@abstractmethod
def renderable(self) -> RenderableType:
"""Get the renderable layout.
Property that derived layouts must override.
"""
[docs]class BlinkLayout(BaseLayout):
"""Blink cli layout."""
_spinner: Optional[Spinner] = None
@property
def spinner(self) -> Spinner:
"""Blink spinner status annimation."""
if not self._spinner:
self._spinner = Spinner(
"dots", text=Text("Announcing services (Press CTRL+C to quit) ...", style="green")
)
return self._spinner
@property
def renderable(self) -> RenderableType:
"""Blink renderable layout (spinner with status)."""
layout = Table.grid(padding=1, expand=True)
layout.add_row(self.spinner)
return layout
[docs]class ListenLayout(BaseLayout):
"""Listen cli layout."""
services: Dict[str, Any] = {}
TABLE_SERVICES_COLUMNS = [
"#",
"Type",
"Name",
"Address IPv4",
"Port",
"Server",
"TTL",
]
_spinner: Optional[Spinner] = None
@property
def spinner(self) -> Spinner:
"""Listen spinner status annimation."""
if not self._spinner:
self._spinner = Spinner(
"dots", text=Text("Listen for services (Press CTRL+C to quit) ...", style="green")
)
return self._spinner
@property
def services_table(self) -> Table:
"""Listen services table."""
table = Table(expand=True)
table.title = (
"\n"
":police_car_light::satellite_antenna:"
" mDNS Beacon Listener "
":satellite_antenna::police_car_light:"
)
for key in self.TABLE_SERVICES_COLUMNS:
table.add_column(key, no_wrap=True)
for index, service in enumerate(self.services.values()):
table.add_row(
str(index),
*[str(v) for k, v in service.items() if k in self.TABLE_SERVICES_COLUMNS],
)
return table
@property
def renderable(self) -> RenderableType:
"""Listen renderable layout (a table with spinner and status)."""
layout = Table.grid(padding=1, expand=True)
layout.add_row(self.services_table)
layout.add_row(self.spinner)
return layout
[docs] def update_services(
self, zeroconf: Zeroconf, service_type: str, name: str, state_change: ServiceStateChange
) -> None:
"""On service state change handler."""
service_id = f"{name}_{service_type}"
if state_change is ServiceStateChange.Removed:
self.services.pop(service_id, None)
else:
info = zeroconf.get_service_info(service_type, name)
if info:
self.services[service_id] = {
"Type": info.type,
"Name": info.name,
"Address IPv4": ",".join(info.parsed_addresses(IPVersion.V4Only)),
"Port": info.port,
"Server": info.server,
"TTL": info.host_ttl,
}
self.live.update(self.renderable)