working 2.0 clone
This commit is contained in:
107
custom_components/remote_homeassistant/proxy_services.py
Normal file
107
custom_components/remote_homeassistant/proxy_services.py
Normal file
@@ -0,0 +1,107 @@
|
||||
"""Support for proxy services."""
|
||||
import asyncio
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.service import SERVICE_DESCRIPTION_CACHE
|
||||
|
||||
from .const import CONF_SERVICE_PREFIX, CONF_SERVICES, SERVICE_CALL_LIMIT
|
||||
|
||||
|
||||
class ProxyServices:
|
||||
"""Manages remote proxy services."""
|
||||
|
||||
def __init__(self, hass, entry, remote):
|
||||
"""Initialize a new ProxyServices instance."""
|
||||
self.hass = hass
|
||||
self.entry = entry
|
||||
self.remote = remote
|
||||
self.remote_services = {}
|
||||
self.registered_services = []
|
||||
|
||||
@property
|
||||
def services(self):
|
||||
"""Return list of service names."""
|
||||
result = []
|
||||
for domain, services in self.remote_services.items():
|
||||
for service in services.keys():
|
||||
result.append(f"{domain}.{service}")
|
||||
return sorted(result)
|
||||
|
||||
async def load(self):
|
||||
"""Call to make initial registration of services."""
|
||||
await self.remote.call(self._async_got_services, "get_services")
|
||||
|
||||
async def unload(self):
|
||||
"""Call to unregister all registered services."""
|
||||
description_cache = self.hass.data[SERVICE_DESCRIPTION_CACHE]
|
||||
|
||||
for domain, service_name in self.registered_services:
|
||||
self.hass.services.async_remove(domain, service_name)
|
||||
|
||||
# Remove from internal description cache
|
||||
service = f"{domain}.{service_name}"
|
||||
if service in description_cache:
|
||||
del description_cache[service]
|
||||
|
||||
async def _async_got_services(self, message):
|
||||
"""Called when list of remote services is available."""
|
||||
self.remote_services = message["result"]
|
||||
|
||||
# A service prefix is needed to not clash with original service names
|
||||
service_prefix = self.entry.options.get(CONF_SERVICE_PREFIX)
|
||||
if not service_prefix:
|
||||
return
|
||||
|
||||
description_cache = self.hass.data[SERVICE_DESCRIPTION_CACHE]
|
||||
for service in self.entry.options.get(CONF_SERVICES, []):
|
||||
domain, service_name = service.split(".")
|
||||
service = service_prefix + service_name
|
||||
|
||||
# Register new service with same name as original service but with prefix
|
||||
self.hass.services.async_register(
|
||||
domain,
|
||||
service,
|
||||
self._async_handle_service_call,
|
||||
vol.Schema({}, extra=vol.ALLOW_EXTRA),
|
||||
)
|
||||
|
||||
# <HERE_BE_DRAGON>
|
||||
# Service metadata can only be provided via a services.yaml file for a
|
||||
# particular component, something not possible here. A cache is used
|
||||
# internally for loaded service descriptions and that's abused here. If
|
||||
# the internal representation of the cache change, this sill break.
|
||||
# </HERE_BE_DRAGONS>
|
||||
service_info = self.remote_services.get(domain, {}).get(service_name)
|
||||
if service_info:
|
||||
description_cache[f"{domain}.{service}"] = service_info
|
||||
|
||||
self.registered_services.append((domain, service))
|
||||
|
||||
async def _async_handle_service_call(self, event):
|
||||
"""Handle service call to proxy service."""
|
||||
# An eception must be raised from the service call handler (thus method) in
|
||||
# order to end up in the frontend. The code below synchronizes reception of
|
||||
# the service call result, so potential error message can be used as exception
|
||||
# message. Not very pretty...
|
||||
ev = asyncio.Event()
|
||||
res = None
|
||||
|
||||
def _resp(message):
|
||||
nonlocal res
|
||||
res = message
|
||||
ev.set()
|
||||
|
||||
service_prefix = self.entry.options.get(CONF_SERVICE_PREFIX)
|
||||
service = event.service[len(service_prefix) :]
|
||||
await self.remote.call(
|
||||
_resp,
|
||||
"call_service",
|
||||
domain=event.domain,
|
||||
service=service,
|
||||
service_data=event.data.copy(),
|
||||
)
|
||||
|
||||
await asyncio.wait_for(ev.wait(), SERVICE_CALL_LIMIT)
|
||||
if not res["success"]:
|
||||
raise HomeAssistantError(res["error"]["message"])
|
||||
Reference in New Issue
Block a user