From 05ee15c28c90c22e919f3f621fa198e4ff14a436 Mon Sep 17 00:00:00 2001
From: jjlawren <jjlawren@users.noreply.github.com>
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
-- 
GitLab