Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Plex config options support #26870

Merged
merged 5 commits into from Sep 26, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 12 additions & 5 deletions homeassistant/components/plex/__init__.py
Expand Up @@ -77,7 +77,7 @@ def _setup_plex(hass, config):
"""Pass configuration to a config flow."""
server_config = dict(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:
prefix = "https" if server_config.pop(CONF_SSL) else "http"
server_config[
Expand All @@ -96,7 +96,15 @@ async def async_setup_entry(hass, entry):
"""Set up Plex from a config entry."""
server_config = entry.data[PLEX_SERVER_CONFIG]

plex_server = PlexServer(server_config)
if MP_DOMAIN not in entry.options:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you add tests for this as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd eventually like to add tests for files outside of config_flow.py, but I'm not there quite yet.

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:
await hass.async_add_executor_job(plex_server.connect)
except requests.exceptions.ConnectionError as error:
Expand All @@ -123,14 +131,13 @@ async def async_setup_entry(hass, entry):
)
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:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform)
)

entry.add_update_listener(plex_server.async_options_updated)

return True


Expand Down
49 changes: 49 additions & 0 deletions homeassistant/components/plex/config_flow.py
@@ -1,11 +1,13 @@
"""Config flow for Plex."""
import copy
import logging

import plexapi.exceptions
import requests.exceptions
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
from homeassistant.const import (
CONF_HOST,
CONF_PORT,
Expand All @@ -20,6 +22,8 @@
from .const import ( # pylint: disable=unused-import
CONF_SERVER,
CONF_SERVER_IDENTIFIER,
CONF_USE_EPISODE_ART,
CONF_SHOW_ALL_CONTROLS,
DEFAULT_PORT,
DEFAULT_SSL,
DEFAULT_VERIFY_SSL,
Expand Down Expand Up @@ -52,6 +56,12 @@ class PlexFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
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):
"""Initialize the Plex flow."""
self.current_login = {}
Expand Down Expand Up @@ -214,3 +224,42 @@ async def async_step_import(self, import_config):
"""Import from Plex configuration."""
_LOGGER.debug("Imported Plex configuration")
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,
}
),
)
20 changes: 8 additions & 12 deletions homeassistant/components/plex/media_player.py
Expand Up @@ -33,12 +33,9 @@
from homeassistant.util import dt as dt_util

from .const import (
CONF_USE_EPISODE_ART,
CONF_SHOW_ALL_CONTROLS,
CONF_SERVER_IDENTIFIER,
DOMAIN as PLEX_DOMAIN,
NAME_FORMAT,
PLEX_MEDIA_PLAYER_OPTIONS,
REFRESH_LISTENERS,
SERVERS,
)
Expand Down Expand Up @@ -67,8 +64,6 @@ def add_entities(entities, update_before_add=False):
def _setup_platform(hass, config_entry, add_entities_callback):
"""Set up the Plex media_player platform."""
server_id = config_entry.data[CONF_SERVER_IDENTIFIER]
config = hass.data[PLEX_MEDIA_PLAYER_OPTIONS]

plexserver = hass.data[PLEX_DOMAIN][SERVERS][server_id]
plex_clients = {}
plex_sessions = {}
Expand Down Expand Up @@ -102,7 +97,7 @@ def update_devices():

if device.machineIdentifier not in plex_clients:
new_client = PlexClient(
config, device, None, plex_sessions, update_devices
plexserver, device, None, plex_sessions, update_devices
)
plex_clients[device.machineIdentifier] = new_client
_LOGGER.debug("New device: %s", device.machineIdentifier)
Expand Down Expand Up @@ -141,7 +136,7 @@ def update_devices():
and machine_identifier is not None
):
new_client = PlexClient(
config, player, session, plex_sessions, update_devices
plexserver, player, session, plex_sessions, update_devices
)
plex_clients[machine_identifier] = new_client
_LOGGER.debug("New session: %s", machine_identifier)
Expand Down Expand Up @@ -170,7 +165,7 @@ def update_devices():
class PlexClient(MediaPlayerDevice):
"""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."""
self._app_name = ""
self._device = None
Expand All @@ -191,7 +186,7 @@ def __init__(self, config, device, session, plex_sessions, update_devices):
self._state = STATE_IDLE
self._volume_level = 1 # 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.update_devices = update_devices
# General
Expand Down Expand Up @@ -317,8 +312,9 @@ def refresh(self, device, session):

def _set_media_image(self):
thumb_url = self._session.thumbUrl
if self.media_content_type is MEDIA_TYPE_TVSHOW and not self.config.get(
CONF_USE_EPISODE_ART
if (
self.media_content_type is MEDIA_TYPE_TVSHOW
and not self.plex_server.use_episode_art
):
thumb_url = self._session.url(self._session.grandparentThumb)

Expand Down Expand Up @@ -551,7 +547,7 @@ def supported_features(self):
return 0

# force show all controls
if self.config.get(CONF_SHOW_ALL_CONTROLS):
if self.plex_server.show_all_controls:
return (
SUPPORT_PAUSE
| SUPPORT_PREVIOUS_TRACK
Expand Down
30 changes: 28 additions & 2 deletions homeassistant/components/plex/server.py
Expand Up @@ -3,22 +3,38 @@
import plexapi.server
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 .const import CONF_SERVER, DEFAULT_VERIFY_SSL
from .const import (
CONF_SERVER,
CONF_SERVER_IDENTIFIER,
CONF_SHOW_ALL_CONTROLS,
CONF_USE_EPISODE_ART,
DEFAULT_VERIFY_SSL,
DOMAIN as PLEX_DOMAIN,
SERVERS,
)
from .errors import NoServersFound, ServerNotSpecified


class PlexServer:
"""Manages a single Plex server connection."""

def __init__(self, server_config):
def __init__(self, server_config, options=None):
"""Initialize a Plex server instance."""
self._plex_server = None
self._url = server_config.get(CONF_URL)
self._token = server_config.get(CONF_TOKEN)
self._server_name = server_config.get(CONF_SERVER)
self._verify_ssl = server_config.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL)
self.options = options

@staticmethod
async def async_options_updated(hass, entry):
jjlawren marked this conversation as resolved.
Show resolved Hide resolved
"""Triggered by config entry options updates."""
server_id = entry.data[CONF_SERVER_IDENTIFIER]
hass.data[PLEX_DOMAIN][SERVERS][server_id].options = entry.options

def connect(self):
"""Connect to a Plex server directly, obtaining direct URL if necessary."""
Expand Down Expand Up @@ -80,3 +96,13 @@ def machine_identifier(self):
def url_in_use(self):
"""Return URL used for connected Plex server."""
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]
11 changes: 11 additions & 0 deletions homeassistant/components/plex/strings.json
Expand Up @@ -41,5 +41,16 @@
"invalid_import": "Imported configuration is invalid",
"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"
}
}
}
}
}
55 changes: 55 additions & 0 deletions tests/components/plex/test_config_flow.py
Expand Up @@ -28,6 +28,13 @@
MOCK_SERVER_1 = MockAvailableServer(MOCK_NAME_1, MOCK_ID_1)
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):
"""Init a configuration flow."""
Expand Down Expand Up @@ -520,3 +527,51 @@ async def test_manual_config(hass):
== mock_connections.connections[0].httpuri
)
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,
}
}