Add Plex service to refresh a library (#39094)
* Add Plex service to refresh a library * Clean up rebase leftovers * Re-run black * Fix docstring Co-authored-by: Charles Garwood <cgarwood@newdealmultimedia.com> Co-authored-by: Charles Garwood <cgarwood@newdealmultimedia.com>
This commit is contained in:
parent
97602a127a
commit
cb1cf2238d
6 changed files with 195 additions and 2 deletions
|
@ -43,6 +43,7 @@ from .const import (
|
||||||
)
|
)
|
||||||
from .errors import ShouldUpdateConfigEntry
|
from .errors import ShouldUpdateConfigEntry
|
||||||
from .server import PlexServer
|
from .server import PlexServer
|
||||||
|
from .services import async_setup_services
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__package__)
|
_LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
@ -54,6 +55,8 @@ async def async_setup(hass, config):
|
||||||
{SERVERS: {}, DISPATCHERS: {}, WEBSOCKETS: {}, PLATFORMS_COMPLETED: {}},
|
{SERVERS: {}, DISPATCHERS: {}, WEBSOCKETS: {}, PLATFORMS_COMPLETED: {}},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await async_setup_services(hass)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -45,3 +45,4 @@ AUTOMATIC_SETUP_STRING = "Obtain a new token from plex.tv"
|
||||||
MANUAL_SETUP_STRING = "Configure Plex server manually"
|
MANUAL_SETUP_STRING = "Configure Plex server manually"
|
||||||
|
|
||||||
SERVICE_PLAY_ON_SONOS = "play_on_sonos"
|
SERVICE_PLAY_ON_SONOS = "play_on_sonos"
|
||||||
|
SERVICE_REFRESH_LIBRARY = "refresh_library"
|
||||||
|
|
75
homeassistant/components/plex/services.py
Normal file
75
homeassistant/components/plex/services.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
"""Services for the Plex integration."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from plexapi.exceptions import NotFound
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from .const import DOMAIN, SERVERS, SERVICE_REFRESH_LIBRARY
|
||||||
|
|
||||||
|
REFRESH_LIBRARY_SCHEMA = vol.Schema(
|
||||||
|
{vol.Optional("server_name"): str, vol.Required("library_name"): str}
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_services(hass):
|
||||||
|
"""Set up services for the Plex component."""
|
||||||
|
|
||||||
|
async def async_refresh_library_service(service_call):
|
||||||
|
await hass.async_add_executor_job(refresh_library, hass, service_call)
|
||||||
|
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_REFRESH_LIBRARY,
|
||||||
|
async_refresh_library_service,
|
||||||
|
schema=REFRESH_LIBRARY_SCHEMA,
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_library(hass, service_call):
|
||||||
|
"""Scan a Plex library for new and updated media."""
|
||||||
|
plex_server_name = service_call.data.get("server_name")
|
||||||
|
library_name = service_call.data.get("library_name")
|
||||||
|
|
||||||
|
plex_server = get_plex_server(hass, plex_server_name)
|
||||||
|
if not plex_server:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
library = plex_server.library.section(title=library_name)
|
||||||
|
except NotFound:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Library with name '%s' not found in %s",
|
||||||
|
library_name,
|
||||||
|
list(map(lambda x: x.title, plex_server.library.sections())),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.info("Scanning %s for new and updated media", library_name)
|
||||||
|
library.update()
|
||||||
|
|
||||||
|
|
||||||
|
def get_plex_server(hass, plex_server_name=None):
|
||||||
|
"""Retrieve a configured Plex server by name."""
|
||||||
|
plex_servers = hass.data[DOMAIN][SERVERS].values()
|
||||||
|
|
||||||
|
if plex_server_name:
|
||||||
|
plex_server = [x for x in plex_servers if x.friendly_name == plex_server_name]
|
||||||
|
if not plex_server:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Requested Plex server '%s' not found in %s",
|
||||||
|
plex_server_name,
|
||||||
|
list(map(lambda x: x.friendly_name, plex_servers)),
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
elif len(plex_servers) == 1:
|
||||||
|
return next(iter(plex_servers))
|
||||||
|
|
||||||
|
_LOGGER.error(
|
||||||
|
"Multiple Plex servers configured and no selection made: %s",
|
||||||
|
list(map(lambda x: x.friendly_name, plex_servers)),
|
||||||
|
)
|
||||||
|
return None
|
|
@ -11,3 +11,13 @@ play_on_sonos:
|
||||||
media_content_type:
|
media_content_type:
|
||||||
description: The type of content to play. Must be "music".
|
description: The type of content to play. Must be "music".
|
||||||
example: "music"
|
example: "music"
|
||||||
|
|
||||||
|
refresh_library:
|
||||||
|
description: Refresh a Plex library to scan for new and updated media.
|
||||||
|
fields:
|
||||||
|
server_name:
|
||||||
|
description: Name of a Plex server if multiple Plex servers configured.
|
||||||
|
example: "My Plex Server"
|
||||||
|
library_name:
|
||||||
|
description: Name of the Plex library to refresh.
|
||||||
|
example: "TV Shows"
|
||||||
|
|
|
@ -121,8 +121,6 @@ class MockPlexServer:
|
||||||
|
|
||||||
self._systemAccounts = list(map(MockPlexSystemAccount, range(num_users)))
|
self._systemAccounts = list(map(MockPlexSystemAccount, range(num_users)))
|
||||||
|
|
||||||
self._library = None
|
|
||||||
|
|
||||||
self._clients = []
|
self._clients = []
|
||||||
self._sessions = []
|
self._sessions = []
|
||||||
self.set_clients(num_users)
|
self.set_clients(num_users)
|
||||||
|
@ -400,6 +398,10 @@ class MockPlexLibrarySection:
|
||||||
"""Mock the key identifier property."""
|
"""Mock the key identifier property."""
|
||||||
return str(id(self.title))
|
return str(id(self.title))
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Mock the update call."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MockPlexMediaItem:
|
class MockPlexMediaItem:
|
||||||
"""Mock a Plex Media instance."""
|
"""Mock a Plex Media instance."""
|
||||||
|
|
102
tests/components/plex/test_services.py
Normal file
102
tests/components/plex/test_services.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
"""Tests for various Plex services."""
|
||||||
|
from homeassistant.components.plex.const import (
|
||||||
|
CONF_SERVER,
|
||||||
|
CONF_SERVER_IDENTIFIER,
|
||||||
|
DOMAIN,
|
||||||
|
PLEX_SERVER_CONFIG,
|
||||||
|
SERVICE_REFRESH_LIBRARY,
|
||||||
|
)
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_HOST,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_TOKEN,
|
||||||
|
CONF_URL,
|
||||||
|
CONF_VERIFY_SSL,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import DEFAULT_DATA, DEFAULT_OPTIONS, MOCK_SERVERS, MOCK_TOKEN
|
||||||
|
from .mock_classes import MockPlexAccount, MockPlexLibrarySection, MockPlexServer
|
||||||
|
|
||||||
|
from tests.async_mock import patch
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_refresh_library(hass):
|
||||||
|
"""Test refresh_library service call."""
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data=DEFAULT_DATA,
|
||||||
|
options=DEFAULT_OPTIONS,
|
||||||
|
unique_id=DEFAULT_DATA["server_id"],
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_plex_server = MockPlexServer(config_entry=entry)
|
||||||
|
|
||||||
|
with patch("plexapi.server.PlexServer", return_value=mock_plex_server), patch(
|
||||||
|
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
|
||||||
|
), patch("homeassistant.components.plex.PlexWebsocket", autospec=True):
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Test with non-existent server
|
||||||
|
with patch.object(MockPlexLibrarySection, "update") as mock_update:
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_REFRESH_LIBRARY,
|
||||||
|
{"server_name": "Not a Server", "library_name": "Movies"},
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
assert not mock_update.called
|
||||||
|
|
||||||
|
# Test with non-existent library
|
||||||
|
with patch.object(MockPlexLibrarySection, "update") as mock_update:
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_REFRESH_LIBRARY,
|
||||||
|
{"library_name": "Not a Library"},
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
assert not mock_update.called
|
||||||
|
|
||||||
|
# Test with valid library
|
||||||
|
with patch.object(MockPlexLibrarySection, "update") as mock_update:
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_REFRESH_LIBRARY,
|
||||||
|
{"library_name": "Movies"},
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
assert mock_update.called
|
||||||
|
|
||||||
|
# Add a second configured server
|
||||||
|
entry_2 = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={
|
||||||
|
CONF_SERVER: MOCK_SERVERS[1][CONF_SERVER],
|
||||||
|
PLEX_SERVER_CONFIG: {
|
||||||
|
CONF_TOKEN: MOCK_TOKEN,
|
||||||
|
CONF_URL: f"https://{MOCK_SERVERS[1][CONF_HOST]}:{MOCK_SERVERS[1][CONF_PORT]}",
|
||||||
|
CONF_VERIFY_SSL: True,
|
||||||
|
},
|
||||||
|
CONF_SERVER_IDENTIFIER: MOCK_SERVERS[1][CONF_SERVER_IDENTIFIER],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_plex_server_2 = MockPlexServer(config_entry=entry_2)
|
||||||
|
with patch("plexapi.server.PlexServer", return_value=mock_plex_server_2), patch(
|
||||||
|
"plexapi.myplex.MyPlexAccount", return_value=MockPlexAccount()
|
||||||
|
), patch("homeassistant.components.plex.PlexWebsocket", autospec=True):
|
||||||
|
entry_2.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(entry_2.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Test multiple servers available but none specified
|
||||||
|
with patch.object(MockPlexLibrarySection, "update") as mock_update:
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_REFRESH_LIBRARY,
|
||||||
|
{"library_name": "Movies"},
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
assert not mock_update.called
|
Loading…
Add table
Reference in a new issue