"""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)