mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Add Plex config options support (#26870)
* Add config options support * Actually copy dict * Move media_player options to PlexServer class * Handle updated config options * Move callback out of server
This commit is contained in:
parent
62cea2b7ac
commit
9b204ad162
@ -77,7 +77,7 @@ def _setup_plex(hass, config):
|
|||||||
"""Pass configuration to a config flow."""
|
"""Pass configuration to a config flow."""
|
||||||
server_config = dict(config)
|
server_config = dict(config)
|
||||||
if MP_DOMAIN in server_config:
|
if MP_DOMAIN in server_config:
|
||||||
hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = server_config.pop(MP_DOMAIN)
|
hass.data.setdefault(PLEX_MEDIA_PLAYER_OPTIONS, server_config.pop(MP_DOMAIN))
|
||||||
if CONF_HOST in server_config:
|
if CONF_HOST in server_config:
|
||||||
prefix = "https" if server_config.pop(CONF_SSL) else "http"
|
prefix = "https" if server_config.pop(CONF_SSL) else "http"
|
||||||
server_config[
|
server_config[
|
||||||
@ -96,7 +96,15 @@ async def async_setup_entry(hass, entry):
|
|||||||
"""Set up Plex from a config entry."""
|
"""Set up Plex from a config entry."""
|
||||||
server_config = entry.data[PLEX_SERVER_CONFIG]
|
server_config = entry.data[PLEX_SERVER_CONFIG]
|
||||||
|
|
||||||
plex_server = PlexServer(server_config)
|
if MP_DOMAIN not in entry.options:
|
||||||
|
options = dict(entry.options)
|
||||||
|
options.setdefault(
|
||||||
|
MP_DOMAIN,
|
||||||
|
hass.data.get(PLEX_MEDIA_PLAYER_OPTIONS) or MEDIA_PLAYER_SCHEMA({}),
|
||||||
|
)
|
||||||
|
hass.config_entries.async_update_entry(entry, options=options)
|
||||||
|
|
||||||
|
plex_server = PlexServer(server_config, entry.options)
|
||||||
try:
|
try:
|
||||||
await hass.async_add_executor_job(plex_server.connect)
|
await hass.async_add_executor_job(plex_server.connect)
|
||||||
except requests.exceptions.ConnectionError as error:
|
except requests.exceptions.ConnectionError as error:
|
||||||
@ -123,14 +131,13 @@ async def async_setup_entry(hass, entry):
|
|||||||
)
|
)
|
||||||
hass.data[PLEX_DOMAIN][SERVERS][plex_server.machine_identifier] = plex_server
|
hass.data[PLEX_DOMAIN][SERVERS][plex_server.machine_identifier] = plex_server
|
||||||
|
|
||||||
if not hass.data.get(PLEX_MEDIA_PLAYER_OPTIONS):
|
|
||||||
hass.data[PLEX_MEDIA_PLAYER_OPTIONS] = MEDIA_PLAYER_SCHEMA({})
|
|
||||||
|
|
||||||
for platform in PLATFORMS:
|
for platform in PLATFORMS:
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.async_forward_entry_setup(entry, platform)
|
hass.config_entries.async_forward_entry_setup(entry, platform)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
entry.add_update_listener(async_options_updated)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -150,3 +157,9 @@ async def async_unload_entry(hass, entry):
|
|||||||
hass.data[PLEX_DOMAIN][SERVERS].pop(server_id)
|
hass.data[PLEX_DOMAIN][SERVERS].pop(server_id)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_options_updated(hass, entry):
|
||||||
|
"""Triggered by config entry options updates."""
|
||||||
|
server_id = entry.data[CONF_SERVER_IDENTIFIER]
|
||||||
|
hass.data[PLEX_DOMAIN][SERVERS][server_id].options = entry.options
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
"""Config flow for Plex."""
|
"""Config flow for Plex."""
|
||||||
|
import copy
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import plexapi.exceptions
|
import plexapi.exceptions
|
||||||
@ -6,6 +7,7 @@ import requests.exceptions
|
|||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
@ -20,6 +22,8 @@ from homeassistant.util.json import load_json
|
|||||||
from .const import ( # pylint: disable=unused-import
|
from .const import ( # pylint: disable=unused-import
|
||||||
CONF_SERVER,
|
CONF_SERVER,
|
||||||
CONF_SERVER_IDENTIFIER,
|
CONF_SERVER_IDENTIFIER,
|
||||||
|
CONF_USE_EPISODE_ART,
|
||||||
|
CONF_SHOW_ALL_CONTROLS,
|
||||||
DEFAULT_PORT,
|
DEFAULT_PORT,
|
||||||
DEFAULT_SSL,
|
DEFAULT_SSL,
|
||||||
DEFAULT_VERIFY_SSL,
|
DEFAULT_VERIFY_SSL,
|
||||||
@ -52,6 +56,12 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
VERSION = 1
|
VERSION = 1
|
||||||
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@callback
|
||||||
|
def async_get_options_flow(config_entry):
|
||||||
|
"""Get the options flow for this handler."""
|
||||||
|
return PlexOptionsFlowHandler(config_entry)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize the Plex flow."""
|
"""Initialize the Plex flow."""
|
||||||
self.current_login = {}
|
self.current_login = {}
|
||||||
@ -214,3 +224,42 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
"""Import from Plex configuration."""
|
"""Import from Plex configuration."""
|
||||||
_LOGGER.debug("Imported Plex configuration")
|
_LOGGER.debug("Imported Plex configuration")
|
||||||
return await self.async_step_server_validate(import_config)
|
return await self.async_step_server_validate(import_config)
|
||||||
|
|
||||||
|
|
||||||
|
class PlexOptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
|
"""Handle Plex options."""
|
||||||
|
|
||||||
|
def __init__(self, config_entry):
|
||||||
|
"""Initialize Plex options flow."""
|
||||||
|
self.options = copy.deepcopy(config_entry.options)
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input=None):
|
||||||
|
"""Manage the Plex options."""
|
||||||
|
return await self.async_step_plex_mp_settings()
|
||||||
|
|
||||||
|
async def async_step_plex_mp_settings(self, user_input=None):
|
||||||
|
"""Manage the Plex media_player options."""
|
||||||
|
if user_input is not None:
|
||||||
|
self.options[MP_DOMAIN][CONF_USE_EPISODE_ART] = user_input[
|
||||||
|
CONF_USE_EPISODE_ART
|
||||||
|
]
|
||||||
|
self.options[MP_DOMAIN][CONF_SHOW_ALL_CONTROLS] = user_input[
|
||||||
|
CONF_SHOW_ALL_CONTROLS
|
||||||
|
]
|
||||||
|
return self.async_create_entry(title="", data=self.options)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="plex_mp_settings",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_USE_EPISODE_ART,
|
||||||
|
default=self.options[MP_DOMAIN][CONF_USE_EPISODE_ART],
|
||||||
|
): bool,
|
||||||
|
vol.Required(
|
||||||
|
CONF_SHOW_ALL_CONTROLS,
|
||||||
|
default=self.options[MP_DOMAIN][CONF_SHOW_ALL_CONTROLS],
|
||||||
|
): bool,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@ -33,12 +33,9 @@ from homeassistant.helpers.event import track_time_interval
|
|||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_USE_EPISODE_ART,
|
|
||||||
CONF_SHOW_ALL_CONTROLS,
|
|
||||||
CONF_SERVER_IDENTIFIER,
|
CONF_SERVER_IDENTIFIER,
|
||||||
DOMAIN as PLEX_DOMAIN,
|
DOMAIN as PLEX_DOMAIN,
|
||||||
NAME_FORMAT,
|
NAME_FORMAT,
|
||||||
PLEX_MEDIA_PLAYER_OPTIONS,
|
|
||||||
REFRESH_LISTENERS,
|
REFRESH_LISTENERS,
|
||||||
SERVERS,
|
SERVERS,
|
||||||
)
|
)
|
||||||
@ -67,8 +64,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
|||||||
def _setup_platform(hass, config_entry, add_entities_callback):
|
def _setup_platform(hass, config_entry, add_entities_callback):
|
||||||
"""Set up the Plex media_player platform."""
|
"""Set up the Plex media_player platform."""
|
||||||
server_id = config_entry.data[CONF_SERVER_IDENTIFIER]
|
server_id = config_entry.data[CONF_SERVER_IDENTIFIER]
|
||||||
config = hass.data[PLEX_MEDIA_PLAYER_OPTIONS]
|
|
||||||
|
|
||||||
plexserver = hass.data[PLEX_DOMAIN][SERVERS][server_id]
|
plexserver = hass.data[PLEX_DOMAIN][SERVERS][server_id]
|
||||||
plex_clients = {}
|
plex_clients = {}
|
||||||
plex_sessions = {}
|
plex_sessions = {}
|
||||||
@ -102,7 +97,7 @@ def _setup_platform(hass, config_entry, add_entities_callback):
|
|||||||
|
|
||||||
if device.machineIdentifier not in plex_clients:
|
if device.machineIdentifier not in plex_clients:
|
||||||
new_client = PlexClient(
|
new_client = PlexClient(
|
||||||
config, device, None, plex_sessions, update_devices
|
plexserver, device, None, plex_sessions, update_devices
|
||||||
)
|
)
|
||||||
plex_clients[device.machineIdentifier] = new_client
|
plex_clients[device.machineIdentifier] = new_client
|
||||||
_LOGGER.debug("New device: %s", device.machineIdentifier)
|
_LOGGER.debug("New device: %s", device.machineIdentifier)
|
||||||
@ -141,7 +136,7 @@ def _setup_platform(hass, config_entry, add_entities_callback):
|
|||||||
and machine_identifier is not None
|
and machine_identifier is not None
|
||||||
):
|
):
|
||||||
new_client = PlexClient(
|
new_client = PlexClient(
|
||||||
config, player, session, plex_sessions, update_devices
|
plexserver, player, session, plex_sessions, update_devices
|
||||||
)
|
)
|
||||||
plex_clients[machine_identifier] = new_client
|
plex_clients[machine_identifier] = new_client
|
||||||
_LOGGER.debug("New session: %s", machine_identifier)
|
_LOGGER.debug("New session: %s", machine_identifier)
|
||||||
@ -170,7 +165,7 @@ def _setup_platform(hass, config_entry, add_entities_callback):
|
|||||||
class PlexClient(MediaPlayerDevice):
|
class PlexClient(MediaPlayerDevice):
|
||||||
"""Representation of a Plex device."""
|
"""Representation of a Plex device."""
|
||||||
|
|
||||||
def __init__(self, config, device, session, plex_sessions, update_devices):
|
def __init__(self, plex_server, device, session, plex_sessions, update_devices):
|
||||||
"""Initialize the Plex device."""
|
"""Initialize the Plex device."""
|
||||||
self._app_name = ""
|
self._app_name = ""
|
||||||
self._device = None
|
self._device = None
|
||||||
@ -191,7 +186,7 @@ class PlexClient(MediaPlayerDevice):
|
|||||||
self._state = STATE_IDLE
|
self._state = STATE_IDLE
|
||||||
self._volume_level = 1 # since we can't retrieve remotely
|
self._volume_level = 1 # since we can't retrieve remotely
|
||||||
self._volume_muted = False # since we can't retrieve remotely
|
self._volume_muted = False # since we can't retrieve remotely
|
||||||
self.config = config
|
self.plex_server = plex_server
|
||||||
self.plex_sessions = plex_sessions
|
self.plex_sessions = plex_sessions
|
||||||
self.update_devices = update_devices
|
self.update_devices = update_devices
|
||||||
# General
|
# General
|
||||||
@ -317,8 +312,9 @@ class PlexClient(MediaPlayerDevice):
|
|||||||
|
|
||||||
def _set_media_image(self):
|
def _set_media_image(self):
|
||||||
thumb_url = self._session.thumbUrl
|
thumb_url = self._session.thumbUrl
|
||||||
if self.media_content_type is MEDIA_TYPE_TVSHOW and not self.config.get(
|
if (
|
||||||
CONF_USE_EPISODE_ART
|
self.media_content_type is MEDIA_TYPE_TVSHOW
|
||||||
|
and not self.plex_server.use_episode_art
|
||||||
):
|
):
|
||||||
thumb_url = self._session.url(self._session.grandparentThumb)
|
thumb_url = self._session.url(self._session.grandparentThumb)
|
||||||
|
|
||||||
@ -551,7 +547,7 @@ class PlexClient(MediaPlayerDevice):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
# force show all controls
|
# force show all controls
|
||||||
if self.config.get(CONF_SHOW_ALL_CONTROLS):
|
if self.plex_server.show_all_controls:
|
||||||
return (
|
return (
|
||||||
SUPPORT_PAUSE
|
SUPPORT_PAUSE
|
||||||
| SUPPORT_PREVIOUS_TRACK
|
| SUPPORT_PREVIOUS_TRACK
|
||||||
|
@ -3,22 +3,29 @@ import plexapi.myplex
|
|||||||
import plexapi.server
|
import plexapi.server
|
||||||
from requests import Session
|
from requests import Session
|
||||||
|
|
||||||
|
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
|
||||||
from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL
|
from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL
|
||||||
|
|
||||||
from .const import CONF_SERVER, DEFAULT_VERIFY_SSL
|
from .const import (
|
||||||
|
CONF_SERVER,
|
||||||
|
CONF_SHOW_ALL_CONTROLS,
|
||||||
|
CONF_USE_EPISODE_ART,
|
||||||
|
DEFAULT_VERIFY_SSL,
|
||||||
|
)
|
||||||
from .errors import NoServersFound, ServerNotSpecified
|
from .errors import NoServersFound, ServerNotSpecified
|
||||||
|
|
||||||
|
|
||||||
class PlexServer:
|
class PlexServer:
|
||||||
"""Manages a single Plex server connection."""
|
"""Manages a single Plex server connection."""
|
||||||
|
|
||||||
def __init__(self, server_config):
|
def __init__(self, server_config, options=None):
|
||||||
"""Initialize a Plex server instance."""
|
"""Initialize a Plex server instance."""
|
||||||
self._plex_server = None
|
self._plex_server = None
|
||||||
self._url = server_config.get(CONF_URL)
|
self._url = server_config.get(CONF_URL)
|
||||||
self._token = server_config.get(CONF_TOKEN)
|
self._token = server_config.get(CONF_TOKEN)
|
||||||
self._server_name = server_config.get(CONF_SERVER)
|
self._server_name = server_config.get(CONF_SERVER)
|
||||||
self._verify_ssl = server_config.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL)
|
self._verify_ssl = server_config.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL)
|
||||||
|
self.options = options
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""Connect to a Plex server directly, obtaining direct URL if necessary."""
|
"""Connect to a Plex server directly, obtaining direct URL if necessary."""
|
||||||
@ -80,3 +87,13 @@ class PlexServer:
|
|||||||
def url_in_use(self):
|
def url_in_use(self):
|
||||||
"""Return URL used for connected Plex server."""
|
"""Return URL used for connected Plex server."""
|
||||||
return self._plex_server._baseurl # pylint: disable=W0212
|
return self._plex_server._baseurl # pylint: disable=W0212
|
||||||
|
|
||||||
|
@property
|
||||||
|
def use_episode_art(self):
|
||||||
|
"""Return use_episode_art option."""
|
||||||
|
return self.options[MP_DOMAIN][CONF_USE_EPISODE_ART]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def show_all_controls(self):
|
||||||
|
"""Return show_all_controls option."""
|
||||||
|
return self.options[MP_DOMAIN][CONF_SHOW_ALL_CONTROLS]
|
||||||
|
@ -41,5 +41,16 @@
|
|||||||
"invalid_import": "Imported configuration is invalid",
|
"invalid_import": "Imported configuration is invalid",
|
||||||
"unknown": "Failed for unknown reason"
|
"unknown": "Failed for unknown reason"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"step": {
|
||||||
|
"plex_mp_settings": {
|
||||||
|
"description": "Options for Plex Media Players",
|
||||||
|
"data": {
|
||||||
|
"use_episode_art": "Use episode art",
|
||||||
|
"show_all_controls": "Show all controls"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,13 @@ MOCK_FILE_CONTENTS = {
|
|||||||
MOCK_SERVER_1 = MockAvailableServer(MOCK_NAME_1, MOCK_ID_1)
|
MOCK_SERVER_1 = MockAvailableServer(MOCK_NAME_1, MOCK_ID_1)
|
||||||
MOCK_SERVER_2 = MockAvailableServer(MOCK_NAME_2, MOCK_ID_2)
|
MOCK_SERVER_2 = MockAvailableServer(MOCK_NAME_2, MOCK_ID_2)
|
||||||
|
|
||||||
|
DEFAULT_OPTIONS = {
|
||||||
|
config_flow.MP_DOMAIN: {
|
||||||
|
config_flow.CONF_USE_EPISODE_ART: False,
|
||||||
|
config_flow.CONF_SHOW_ALL_CONTROLS: False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def init_config_flow(hass):
|
def init_config_flow(hass):
|
||||||
"""Init a configuration flow."""
|
"""Init a configuration flow."""
|
||||||
@ -520,3 +527,51 @@ async def test_manual_config(hass):
|
|||||||
== mock_connections.connections[0].httpuri
|
== mock_connections.connections[0].httpuri
|
||||||
)
|
)
|
||||||
assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN
|
assert result["data"][config_flow.PLEX_SERVER_CONFIG][CONF_TOKEN] == MOCK_TOKEN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_no_token(hass):
|
||||||
|
"""Test failing when no token provided."""
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
config_flow.DOMAIN, context={"source": "user"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], user_input={"manual_setup": False}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"][CONF_TOKEN] == "no_token"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_option_flow(hass):
|
||||||
|
"""Test config flow selection of one of two bridges."""
|
||||||
|
|
||||||
|
entry = MockConfigEntry(domain=config_flow.DOMAIN, data={}, options=DEFAULT_OPTIONS)
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.flow.async_init(
|
||||||
|
entry.entry_id, context={"source": "test"}, data=None
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "plex_mp_settings"
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
config_flow.CONF_USE_EPISODE_ART: True,
|
||||||
|
config_flow.CONF_SHOW_ALL_CONTROLS: True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["data"] == {
|
||||||
|
config_flow.MP_DOMAIN: {
|
||||||
|
config_flow.CONF_USE_EPISODE_ART: True,
|
||||||
|
config_flow.CONF_SHOW_ALL_CONTROLS: True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user