From 1658d530e107331c0fdb0596f6714ad5f592f45a Mon Sep 17 00:00:00 2001 From: jjlawren Date: Tue, 22 Feb 2022 18:34:48 -0600 Subject: [PATCH] Add Plex scan_clients button, enable autoscan (#67055) Co-authored-by: Robert Svensson --- homeassistant/components/plex/__init__.py | 15 +++++++ homeassistant/components/plex/button.py | 53 +++++++++++++++++++++++ homeassistant/components/plex/const.py | 5 ++- homeassistant/components/plex/services.py | 5 ++- tests/components/plex/test_button.py | 36 +++++++++++++++ tests/components/plex/test_init.py | 17 ++++++++ 6 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 homeassistant/components/plex/button.py create mode 100644 tests/components/plex/test_button.py diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 44bca818333..df3a4b8cd11 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -25,10 +25,12 @@ from homeassistant.helpers.dispatcher import ( async_dispatcher_connect, async_dispatcher_send, ) +from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.network import is_internal_request from homeassistant.helpers.typing import ConfigType from .const import ( + CLIENT_SCAN_INTERVAL, CONF_SERVER, CONF_SERVER_IDENTIFIER, DISPATCHERS, @@ -247,6 +249,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await hass.async_add_executor_job(get_plex_account, plex_server) + @callback + def scheduled_client_scan(_): + _LOGGER.debug("Scheduled scan for new clients on %s", plex_server.friendly_name) + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + + entry.async_on_unload( + async_track_time_interval( + hass, + scheduled_client_scan, + CLIENT_SCAN_INTERVAL, + ) + ) + return True diff --git a/homeassistant/components/plex/button.py b/homeassistant/components/plex/button.py new file mode 100644 index 00000000000..23e8ec103e9 --- /dev/null +++ b/homeassistant/components/plex/button.py @@ -0,0 +1,53 @@ +"""Representation of Plex buttons.""" +from __future__ import annotations + +from homeassistant.components.button import ButtonEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.entity import DeviceInfo, EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ( + CONF_SERVER, + CONF_SERVER_IDENTIFIER, + DOMAIN, + PLEX_UPDATE_PLATFORMS_SIGNAL, +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Plex button from config entry.""" + server_id: str = config_entry.data[CONF_SERVER_IDENTIFIER] + server_name: str = config_entry.data[CONF_SERVER] + async_add_entities([PlexScanClientsButton(server_id, server_name)]) + + +class PlexScanClientsButton(ButtonEntity): + """Representation of a scan_clients button entity.""" + + _attr_entity_category = EntityCategory.CONFIG + + def __init__(self, server_id: str, server_name: str) -> None: + """Initialize a scan_clients Plex button entity.""" + self.server_id = server_id + self._attr_name = f"Scan Clients ({server_name})" + self._attr_unique_id = f"plex-scan_clients-{self.server_id}" + + async def async_press(self) -> None: + """Press the button.""" + async_dispatcher_send( + self.hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(self.server_id) + ) + + @property + def device_info(self) -> DeviceInfo: + """Return a device description for device registry.""" + return DeviceInfo( + identifiers={(DOMAIN, self.server_id)}, + manufacturer="Plex", + ) diff --git a/homeassistant/components/plex/const.py b/homeassistant/components/plex/const.py index ce28357a4b1..dea976f46dd 100644 --- a/homeassistant/components/plex/const.py +++ b/homeassistant/components/plex/const.py @@ -1,4 +1,6 @@ """Constants for the Plex component.""" +from datetime import timedelta + from homeassistant.const import Platform, __version__ DOMAIN = "plex" @@ -12,11 +14,12 @@ DEFAULT_VERIFY_SSL = True PLEXTV_THROTTLE = 60 +CLIENT_SCAN_INTERVAL = timedelta(minutes=10) DEBOUNCE_TIMEOUT = 1 DISPATCHERS = "dispatchers" GDM_DEBOUNCER = "gdm_debouncer" GDM_SCANNER = "gdm_scanner" -PLATFORMS = frozenset([Platform.MEDIA_PLAYER, Platform.SENSOR]) +PLATFORMS = frozenset([Platform.BUTTON, Platform.MEDIA_PLAYER, Platform.SENSOR]) PLATFORMS_COMPLETED = "platforms_completed" PLAYER_SOURCE = "player_source" SERVERS = "servers" diff --git a/homeassistant/components/plex/services.py b/homeassistant/components/plex/services.py index 11d40dbab75..0433ba836cd 100644 --- a/homeassistant/components/plex/services.py +++ b/homeassistant/components/plex/services.py @@ -31,7 +31,10 @@ async def async_setup_services(hass): await hass.async_add_executor_job(refresh_library, hass, service_call) async def async_scan_clients_service(_: ServiceCall) -> None: - _LOGGER.debug("Scanning for new Plex clients") + _LOGGER.warning( + "This service is deprecated in favor of the scan_clients button entity. " + "Service calls will still work for now but the service will be removed in a future release" + ) for server_id in hass.data[DOMAIN][SERVERS]: async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) diff --git a/tests/components/plex/test_button.py b/tests/components/plex/test_button.py new file mode 100644 index 00000000000..b540ba2d031 --- /dev/null +++ b/tests/components/plex/test_button.py @@ -0,0 +1,36 @@ +"""Tests for Plex buttons.""" +from datetime import timedelta +from unittest.mock import patch + +from homeassistant.components.button.const import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS +from homeassistant.components.plex.const import DEBOUNCE_TIMEOUT +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.util import dt + +from tests.common import async_fire_time_changed + + +async def test_scan_clients_button_schedule(hass, setup_plex_server): + """Test scan_clients button scheduled update.""" + with patch( + "homeassistant.components.plex.server.PlexServer._async_update_platforms" + ) as mock_scan_clients: + await setup_plex_server() + mock_scan_clients.reset_mock() + + async_fire_time_changed( + hass, + dt.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT), + ) + + assert await hass.services.async_call( + BUTTON_DOMAIN, + SERVICE_PRESS, + { + ATTR_ENTITY_ID: "button.scan_clients_plex_server_1", + }, + True, + ) + await hass.async_block_till_done() + + assert mock_scan_clients.called diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py index fced4bae58a..8e09f702386 100644 --- a/tests/components/plex/test_init.py +++ b/tests/components/plex/test_init.py @@ -276,3 +276,20 @@ async def test_bad_token_with_tokenless_server( # Ensure updates that rely on account return nothing trigger_plex_update(mock_websocket) await hass.async_block_till_done() + + +async def test_scan_clients_schedule(hass, setup_plex_server): + """Test scan_clients scheduled update.""" + with patch( + "homeassistant.components.plex.server.PlexServer._async_update_platforms" + ) as mock_scan_clients: + await setup_plex_server() + mock_scan_clients.reset_mock() + + async_fire_time_changed( + hass, + dt_util.utcnow() + const.CLIENT_SCAN_INTERVAL, + ) + await hass.async_block_till_done() + + assert mock_scan_clients.called