File: mdns.py

package info (click to toggle)
python-netdisco 2.8.2-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 508 kB
  • sloc: python: 1,532; xml: 247; sh: 10; makefile: 8
file content (84 lines) | stat: -rw-r--r-- 2,928 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""Add support for discovering mDNS services."""
import logging
from typing import List  # noqa: F401

from zeroconf import DNSPointer, DNSRecord
from zeroconf import Error as ZeroconfError
from zeroconf import ServiceBrowser, ServiceInfo, ServiceStateChange, Zeroconf

_LOGGER = logging.getLogger(__name__)


class FastServiceBrowser(ServiceBrowser):
    """ServiceBrowser that does not process record updates."""

    def update_record(self, zc: Zeroconf, now: float, record: DNSRecord) -> None:
        """Ignore record updates for non-ptrs."""
        if record.name not in self.types or not isinstance(record, DNSPointer):
            return
        super().update_record(zc, now, record)


class MDNS:
    """Base class to discover mDNS services."""

    def __init__(self, zeroconf_instance=None):
        """Initialize the discovery."""
        self.zeroconf = zeroconf_instance
        self._created_zeroconf = False
        self.services = []  # type: List[ServiceInfo]
        self._browser = None  # type: ServiceBrowser

    def register_service(self, service):
        """Register a mDNS service."""
        self.services.append(service)

    def start(self):
        """Start discovery."""
        try:
            if not self.zeroconf:
                self.zeroconf = Zeroconf()
                self._created_zeroconf = True

            services_by_type = {}

            for service in self.services:
                services_by_type.setdefault(service.typ, [])
                services_by_type[service.typ].append(service)

            def _service_update(zeroconf, service_type, name, state_change):
                if state_change == ServiceStateChange.Added:
                    for service in services_by_type[service_type]:
                        try:
                            service.add_service(zeroconf, service_type, name)
                        except ZeroconfError:
                            _LOGGER.exception("Failed to add service %s", name)
                elif state_change == ServiceStateChange.Removed:
                    for service in services_by_type[service_type]:
                        service.remove_service(zeroconf, service_type, name)

            types = [service.typ for service in self.services]
            self._browser = FastServiceBrowser(
                self.zeroconf, types, handlers=[_service_update]
            )
        except Exception:  # pylint: disable=broad-except
            self.stop()
            raise

    def stop(self):
        """Stop discovering."""
        if self._browser:
            self._browser.cancel()
            self._browser = None

        for service in self.services:
            service.reset()

        if self._created_zeroconf:
            self.zeroconf.close()
            self.zeroconf = None

    @property
    def entries(self):
        """Return all entries in the cache."""
        return self.zeroconf.cache.entries()