From 05ee15c28c90c22e919f3f621fa198e4ff14a436 Mon Sep 17 00:00:00 2001 From: jjlawren Date: Fri, 25 Oct 2019 11:37:50 -0500 Subject: [PATCH] Update Plex via websockets (#28158) * Save client identifier from auth for future use * Use websocket events to update Plex * Handle websocket disconnections * Use aiohttp, shut down socket cleanly * Bad rebase fix * Don't connect websocket during config_flow validation, fix tests * Move websocket handling to external library * Close websocket session on HA stop * Use external library, revert unnecessary test change * Async & lint fixes * Clean up websocket stopper on entry unload * Setup websocket in component, pass actual needed object to library --- .coveragerc | 1 + homeassistant/components/plex/__init__.py | 45 ++++++++++++++++----- homeassistant/components/plex/const.py | 3 +- homeassistant/components/plex/manifest.json | 3 +- homeassistant/components/plex/server.py | 7 ++++ requirements_all.txt | 3 ++ requirements_test_all.txt | 3 ++ 7 files changed, 52 insertions(+), 13 deletions(-) diff --git a/.coveragerc b/.coveragerc index f97a7524a21..748ca511dd5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -514,6 +514,7 @@ omit = homeassistant/components/plex/media_player.py homeassistant/components/plex/sensor.py homeassistant/components/plex/server.py + homeassistant/components/plex/websockets.py homeassistant/components/plugwise/* homeassistant/components/plum_lightpad/* homeassistant/components/pocketcasts/sensor.py diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index b6ed3245115..1aaa8a8e3aa 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -1,9 +1,9 @@ """Support to embed Plex.""" import asyncio -from datetime import timedelta import logging import plexapi.exceptions +from plexwebsocket import PlexWebsocket import requests.exceptions import voluptuous as vol @@ -16,9 +16,14 @@ from homeassistant.const import ( CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL, + EVENT_HOMEASSISTANT_STOP, ) from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) from .const import ( CONF_USE_EPISODE_ART, @@ -33,8 +38,9 @@ from .const import ( PLATFORMS, PLEX_MEDIA_PLAYER_OPTIONS, PLEX_SERVER_CONFIG, - REFRESH_LISTENERS, + PLEX_UPDATE_PLATFORMS_SIGNAL, SERVERS, + WEBSOCKETS, ) from .server import PlexServer @@ -67,9 +73,7 @@ _LOGGER = logging.getLogger(__package__) def setup(hass, config): """Set up the Plex component.""" - hass.data.setdefault( - PLEX_DOMAIN, {SERVERS: {}, REFRESH_LISTENERS: {}, DISPATCHERS: {}} - ) + hass.data.setdefault(PLEX_DOMAIN, {SERVERS: {}, DISPATCHERS: {}, WEBSOCKETS: {}}) plex_config = config.get(PLEX_DOMAIN, {}) if plex_config: @@ -136,7 +140,6 @@ async def async_setup_entry(hass, entry): ) server_id = plex_server.machine_identifier hass.data[PLEX_DOMAIN][SERVERS][server_id] = plex_server - hass.data[PLEX_DOMAIN][DISPATCHERS][server_id] = [] for platform in PLATFORMS: hass.async_create_task( @@ -145,9 +148,29 @@ async def async_setup_entry(hass, entry): entry.add_update_listener(async_options_updated) - hass.data[PLEX_DOMAIN][REFRESH_LISTENERS][server_id] = async_track_time_interval( - hass, lambda now: plex_server.update_platforms(), timedelta(seconds=10) + unsub = async_dispatcher_connect( + hass, + PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id), + plex_server.update_platforms, ) + hass.data[PLEX_DOMAIN][DISPATCHERS].setdefault(server_id, []) + hass.data[PLEX_DOMAIN][DISPATCHERS][server_id].append(unsub) + + def update_plex(): + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + + session = async_get_clientsession(hass) + websocket = PlexWebsocket(plex_server.plex_server, update_plex, session) + hass.loop.create_task(websocket.listen()) + hass.data[PLEX_DOMAIN][WEBSOCKETS][server_id] = websocket + + def close_websocket_session(_): + websocket.close() + + unsub = hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, close_websocket_session + ) + hass.data[PLEX_DOMAIN][DISPATCHERS][server_id].append(unsub) return True @@ -156,8 +179,8 @@ async def async_unload_entry(hass, entry): """Unload a config entry.""" server_id = entry.data[CONF_SERVER_IDENTIFIER] - cancel = hass.data[PLEX_DOMAIN][REFRESH_LISTENERS].pop(server_id) - cancel() + websocket = hass.data[PLEX_DOMAIN][WEBSOCKETS].pop(server_id) + websocket.close() dispatchers = hass.data[PLEX_DOMAIN][DISPATCHERS].pop(server_id) for unsub in dispatchers: diff --git a/homeassistant/components/plex/const.py b/homeassistant/components/plex/const.py index 0d512101e11..d3c79e60bc4 100644 --- a/homeassistant/components/plex/const.py +++ b/homeassistant/components/plex/const.py @@ -10,8 +10,8 @@ DEFAULT_VERIFY_SSL = True DISPATCHERS = "dispatchers" PLATFORMS = ["media_player", "sensor"] -REFRESH_LISTENERS = "refresh_listeners" SERVERS = "servers" +WEBSOCKETS = "websockets" PLEX_CONFIG_FILE = "plex.conf" PLEX_MEDIA_PLAYER_OPTIONS = "plex_mp_options" @@ -19,6 +19,7 @@ PLEX_SERVER_CONFIG = "server_config" PLEX_NEW_MP_SIGNAL = "plex_new_mp_signal.{}" PLEX_UPDATE_MEDIA_PLAYER_SIGNAL = "plex_update_mp_signal.{}" +PLEX_UPDATE_PLATFORMS_SIGNAL = "plex_update_platforms_signal.{}" PLEX_UPDATE_SENSOR_SIGNAL = "plex_update_sensor_signal.{}" CONF_CLIENT_IDENTIFIER = "client_id" diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index 3c570a0e64c..90ae305148e 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -5,7 +5,8 @@ "documentation": "https://www.home-assistant.io/integrations/plex", "requirements": [ "plexapi==3.0.6", - "plexauth==0.0.5" + "plexauth==0.0.5", + "plexwebsocket==0.0.1" ], "dependencies": [ "http" diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index c0461ee0f54..e6f77a310f1 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -103,6 +103,8 @@ class PlexServer: def update_platforms(self): """Update the platform entities.""" + _LOGGER.debug("Updating devices") + available_clients = {} new_clients = set() @@ -164,6 +166,11 @@ class PlexServer: sessions, ) + @property + def plex_server(self): + """Return the plexapi PlexServer instance.""" + return self._plex_server + @property def friendly_name(self): """Return name of connected Plex server.""" diff --git a/requirements_all.txt b/requirements_all.txt index 6a6c569b4a1..8f5e83a8e67 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -973,6 +973,9 @@ plexapi==3.0.6 # homeassistant.components.plex plexauth==0.0.5 +# homeassistant.components.plex +plexwebsocket==0.0.1 + # homeassistant.components.plum_lightpad plumlightpad==0.0.11 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 51b7ae9d71f..0af9b338987 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -345,6 +345,9 @@ plexapi==3.0.6 # homeassistant.components.plex plexauth==0.0.5 +# homeassistant.components.plex +plexwebsocket==0.0.1 + # homeassistant.components.mhz19 # homeassistant.components.serial_pm pmsensor==0.4