"""Provides helpful stuff for discoverables."""
|
|
# pylint: disable=abstract-method
|
|
import ipaddress
|
|
from urllib.parse import urlparse
|
|
|
|
from ..const import (
|
|
ATTR_NAME, ATTR_MODEL_NAME, ATTR_HOST, ATTR_PORT, ATTR_SSDP_DESCRIPTION,
|
|
ATTR_SERIAL, ATTR_MODEL_NUMBER, ATTR_HOSTNAME, ATTR_MAC_ADDRESS,
|
|
ATTR_PROPERTIES, ATTR_MANUFACTURER, ATTR_UDN, ATTR_UPNP_DEVICE_TYPE)
|
|
|
|
|
|
class BaseDiscoverable:
|
|
"""Base class for discoverable services or device types."""
|
|
|
|
def is_discovered(self):
|
|
"""Return True if it is discovered."""
|
|
return len(self.get_entries()) > 0
|
|
|
|
def get_info(self):
|
|
"""Return a list with the important info for each item.
|
|
|
|
Uses self.info_from_entry internally.
|
|
"""
|
|
return [self.info_from_entry(entry) for entry in self.get_entries()]
|
|
|
|
# pylint: disable=no-self-use
|
|
def info_from_entry(self, entry):
|
|
"""Return an object with important info from the entry."""
|
|
return entry
|
|
|
|
def get_entries(self):
|
|
"""Return all the discovered entries."""
|
|
raise NotImplementedError()
|
|
|
|
|
|
class SSDPDiscoverable(BaseDiscoverable):
|
|
"""uPnP discoverable base class."""
|
|
|
|
def __init__(self, netdis):
|
|
"""Initialize SSDPDiscoverable."""
|
|
self.netdis = netdis
|
|
|
|
def info_from_entry(self, entry):
|
|
"""Get most important info."""
|
|
url = urlparse(entry.location)
|
|
info = {
|
|
ATTR_HOST: url.hostname,
|
|
ATTR_PORT: url.port,
|
|
ATTR_SSDP_DESCRIPTION: entry.location
|
|
}
|
|
device = entry.description.get('device')
|
|
|
|
if device:
|
|
info[ATTR_NAME] = device.get('friendlyName')
|
|
info[ATTR_MODEL_NAME] = device.get('modelName')
|
|
info[ATTR_MODEL_NUMBER] = device.get('modelNumber')
|
|
info[ATTR_SERIAL] = device.get('serialNumber')
|
|
info[ATTR_MANUFACTURER] = device.get('manufacturer')
|
|
info[ATTR_UDN] = device.get('UDN')
|
|
info[ATTR_UPNP_DEVICE_TYPE] = device.get('deviceType')
|
|
|
|
return info
|
|
|
|
# Helper functions
|
|
|
|
# pylint: disable=invalid-name
|
|
def find_by_st(self, st):
|
|
"""Find entries by ST (the device identifier)."""
|
|
return self.netdis.ssdp.find_by_st(st)
|
|
|
|
def find_by_device_description(self, values):
|
|
"""Find entries based on values from their description."""
|
|
return self.netdis.ssdp.find_by_device_description(values)
|
|
|
|
|
|
class MDNSDiscoverable(BaseDiscoverable):
|
|
"""mDNS Discoverable base class."""
|
|
|
|
def __init__(self, netdis, typ):
|
|
"""Initialize MDNSDiscoverable."""
|
|
self.netdis = netdis
|
|
self.typ = typ
|
|
self.services = {}
|
|
|
|
netdis.mdns.register_service(self)
|
|
|
|
def reset(self):
|
|
"""Reset found services."""
|
|
self.services.clear()
|
|
|
|
# pylint: disable=unused-argument
|
|
def remove_service(self, zconf, typ, name):
|
|
"""Callback when a service is removed."""
|
|
self.services.pop(name, None)
|
|
|
|
def add_service(self, zconf, typ, name):
|
|
"""Callback when a service is found."""
|
|
service = None
|
|
tries = 0
|
|
while service is None and tries < 3:
|
|
service = zconf.get_service_info(typ, name)
|
|
tries += 1
|
|
|
|
if service is not None:
|
|
self.services[name] = service
|
|
|
|
def get_entries(self):
|
|
"""Return all found services."""
|
|
return self.services.values()
|
|
|
|
def info_from_entry(self, entry):
|
|
"""Return most important info from mDNS entries."""
|
|
properties = {}
|
|
|
|
for key, value in entry.properties.items():
|
|
if isinstance(value, bytes):
|
|
value = value.decode('utf-8')
|
|
properties[key.decode('utf-8')] = value
|
|
|
|
info = {
|
|
ATTR_HOST: str(ipaddress.ip_address(entry.address)),
|
|
ATTR_PORT: entry.port,
|
|
ATTR_HOSTNAME: entry.server,
|
|
ATTR_PROPERTIES: properties,
|
|
}
|
|
|
|
if "mac" in properties:
|
|
info[ATTR_MAC_ADDRESS] = properties["mac"]
|
|
|
|
return info
|
|
|
|
def find_by_device_name(self, name):
|
|
"""Find entries based on the beginning of their entry names."""
|
|
return [entry for entry in self.services.values()
|
|
if entry.name.startswith(name)]
|
|
|
|
|
|
class GDMDiscoverable(BaseDiscoverable):
|
|
"""GDM discoverable base class."""
|
|
|
|
def __init__(self, netdis):
|
|
"""Initialize GDMDiscoverable."""
|
|
self.netdis = netdis
|
|
|
|
def find_by_content_type(self, value):
|
|
"""Find entries based on values from their content_type."""
|
|
return self.netdis.gdm.find_by_content_type(value)
|
|
|
|
def find_by_data(self, values):
|
|
"""Find entries based on values from any returned field."""
|
|
return self.netdis.gdm.find_by_data(values)
|