Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions homeassistant/components/plex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
)

from .const import (
CONF_IGNORE_NEW_SHARED_USERS,
CONF_SERVER,
CONF_SERVER_IDENTIFIER,
CONF_SHOW_ALL_CONTROLS,
Expand All @@ -50,6 +51,7 @@
{
vol.Optional(CONF_USE_EPISODE_ART, default=False): cv.boolean,
vol.Optional(CONF_SHOW_ALL_CONTROLS, default=False): cv.boolean,
vol.Optional(CONF_IGNORE_NEW_SHARED_USERS, default=False): cv.boolean,
}
)

Expand Down
48 changes: 46 additions & 2 deletions homeassistant/components/plex/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
from homeassistant.const import CONF_SSL, CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL
from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json

from .const import ( # pylint: disable=unused-import
AUTH_CALLBACK_NAME,
AUTH_CALLBACK_PATH,
CONF_CLIENT_IDENTIFIER,
CONF_IGNORE_NEW_SHARED_USERS,
CONF_MONITORED_USERS,
CONF_SERVER,
CONF_SERVER_IDENTIFIER,
CONF_SHOW_ALL_CONTROLS,
Expand All @@ -28,6 +31,7 @@
DOMAIN,
PLEX_CONFIG_FILE,
PLEX_SERVER_CONFIG,
SERVERS,
X_PLEX_DEVICE_NAME,
X_PLEX_PLATFORM,
X_PLEX_PRODUCT,
Expand Down Expand Up @@ -254,33 +258,73 @@ class PlexOptionsFlowHandler(config_entries.OptionsFlow):
def __init__(self, config_entry):
"""Initialize Plex options flow."""
self.options = copy.deepcopy(config_entry.options)
self.server_id = config_entry.data[CONF_SERVER_IDENTIFIER]
Comment thread
jjlawren marked this conversation as resolved.

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."""
plex_server = self.hass.data[DOMAIN][SERVERS][self.server_id]

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
]
self.options[MP_DOMAIN][CONF_IGNORE_NEW_SHARED_USERS] = user_input[
CONF_IGNORE_NEW_SHARED_USERS
]

account_data = {
user: {"enabled": bool(user in user_input[CONF_MONITORED_USERS])}
for user in plex_server.accounts
}

self.options[MP_DOMAIN][CONF_MONITORED_USERS] = account_data

return self.async_create_entry(title="", data=self.options)

available_accounts = {name: name for name in plex_server.accounts}
available_accounts[plex_server.owner] += " [Owner]"

default_accounts = plex_server.accounts
known_accounts = set(plex_server.option_monitored_users)
if known_accounts:
default_accounts = {
Comment thread
jjlawren marked this conversation as resolved.
user
for user in plex_server.option_monitored_users
if plex_server.option_monitored_users[user]["enabled"]
}
for user in plex_server.accounts:
if user not in known_accounts:
available_accounts[user] += " [New]"
Comment thread
jjlawren marked this conversation as resolved.

if not plex_server.option_ignore_new_shared_users:
for new_user in plex_server.accounts - known_accounts:
default_accounts.add(new_user)

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],
default=plex_server.option_use_episode_art,
): bool,
vol.Required(
CONF_SHOW_ALL_CONTROLS,
default=self.options[MP_DOMAIN][CONF_SHOW_ALL_CONTROLS],
default=plex_server.option_show_all_controls,
): bool,
vol.Optional(
CONF_MONITORED_USERS, default=default_accounts
): cv.multi_select(available_accounts),
vol.Required(
CONF_IGNORE_NEW_SHARED_USERS,
default=plex_server.option_ignore_new_shared_users,
): bool,
}
),
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/plex/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
CONF_SERVER_IDENTIFIER = "server_id"
CONF_USE_EPISODE_ART = "use_episode_art"
CONF_SHOW_ALL_CONTROLS = "show_all_controls"
CONF_IGNORE_NEW_SHARED_USERS = "ignore_new_shared_users"
CONF_MONITORED_USERS = "monitored_users"

AUTH_CALLBACK_PATH = "/auth/plex/callback"
AUTH_CALLBACK_NAME = "auth:plex:callback"
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/plex/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ def _set_media_image(self):
thumb_url = self.session.thumbUrl
if (
self.media_content_type is MEDIA_TYPE_TVSHOW
and not self.plex_server.use_episode_art
and not self.plex_server.option_use_episode_art
):
thumb_url = self.session.url(self.session.grandparentThumb)

Expand Down Expand Up @@ -481,7 +481,7 @@ def make(self):
def supported_features(self):
"""Flag media player features that are supported."""
# force show all controls
if self.plex_server.show_all_controls:
if self.plex_server.option_show_all_controls:
return (
SUPPORT_PAUSE
| SUPPORT_PREVIOUS_TRACK
Expand Down
57 changes: 51 additions & 6 deletions homeassistant/components/plex/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

from .const import (
CONF_CLIENT_IDENTIFIER,
CONF_IGNORE_NEW_SHARED_USERS,
CONF_MONITORED_USERS,
CONF_SERVER,
CONF_SHOW_ALL_CONTROLS,
CONF_USE_EPISODE_ART,
Expand Down Expand Up @@ -51,6 +53,7 @@ def __init__(self, hass, server_config, options=None):
self._verify_ssl = server_config.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL)
self.options = options
self.server_choice = None
self._accounts = []
self._owner_username = None
self._version = None

Expand Down Expand Up @@ -95,6 +98,12 @@ def _connect_with_url():
else:
_connect_with_token()

self._accounts = [
account.name
for account in self._plex_server.systemAccounts()
if account.name
]

owner_account = [
account.name
for account in self._plex_server.systemAccounts()
Expand All @@ -121,8 +130,22 @@ def update_platforms(self):
_LOGGER.debug("Updating devices")

available_clients = {}
ignored_clients = set()
new_clients = set()

monitored_users = self.accounts
known_accounts = set(self.option_monitored_users)
if known_accounts:
monitored_users = {
user
for user in self.option_monitored_users
if self.option_monitored_users[user]["enabled"]
}

if not self.option_ignore_new_shared_users:
for new_user in self.accounts - known_accounts:
monitored_users.add(new_user)

try:
devices = self._plex_server.clients()
sessions = self._plex_server.sessions()
Expand All @@ -147,7 +170,12 @@ def update_platforms(self):
if session.TYPE == "photo":
_LOGGER.debug("Photo session detected, skipping: %s", session)
continue
session_username = session.usernames[0]
for player in session.players:
if session_username not in monitored_users:
ignored_clients.add(player.machineIdentifier)
_LOGGER.debug("Ignoring Plex client owned by %s", session_username)
continue
self._known_idle.discard(player.machineIdentifier)
available_clients.setdefault(
player.machineIdentifier, {"device": player}
Expand All @@ -160,18 +188,20 @@ def update_platforms(self):

new_entity_configs = []
for client_id, client_data in available_clients.items():
if client_id in ignored_clients:
continue
if client_id in new_clients:
new_entity_configs.append(client_data)
else:
self.refresh_entity(
client_id, client_data["device"], client_data.get("session")
)

self._known_clients.update(new_clients)
self._known_clients.update(new_clients | ignored_clients)

idle_clients = (self._known_clients - self._known_idle).difference(
available_clients
)
idle_clients = (
self._known_clients - self._known_idle - ignored_clients
).difference(available_clients)
for client_id in idle_clients:
self.refresh_entity(client_id, None, None)
self._known_idle.add(client_id)
Expand All @@ -194,6 +224,11 @@ def plex_server(self):
"""Return the plexapi PlexServer instance."""
return self._plex_server

@property
def accounts(self):
"""Return accounts associated with the Plex server."""
return set(self._accounts)

@property
def owner(self):
"""Return the Plex server owner username."""
Expand All @@ -220,15 +255,25 @@ def url_in_use(self):
return self._plex_server._baseurl # pylint: disable=protected-access

@property
def use_episode_art(self):
def option_ignore_new_shared_users(self):
"""Return ignore_new_shared_users option."""
return self.options[MP_DOMAIN].get(CONF_IGNORE_NEW_SHARED_USERS, False)

@property
def option_use_episode_art(self):
"""Return use_episode_art option."""
return self.options[MP_DOMAIN][CONF_USE_EPISODE_ART]

@property
def show_all_controls(self):
def option_show_all_controls(self):
"""Return show_all_controls option."""
return self.options[MP_DOMAIN][CONF_SHOW_ALL_CONTROLS]

@property
def option_monitored_users(self):
"""Return dict of monitored users option."""
return self.options[MP_DOMAIN].get(CONF_MONITORED_USERS, {})

@property
def library(self):
"""Return library attribute from server object."""
Expand Down
4 changes: 3 additions & 1 deletion homeassistant/components/plex/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
"description": "Options for Plex Media Players",
"data": {
"use_episode_art": "Use episode art",
"show_all_controls": "Show all controls"
"show_all_controls": "Show all controls",
"ignore_new_shared_users": "Ignore new managed/shared users",
"monitored_users": "Monitored users"
}
}
}
Expand Down
51 changes: 50 additions & 1 deletion tests/components/plex/mock_classes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Mock classes used in tests."""
import itertools

from homeassistant.components.plex.const import CONF_SERVER, CONF_SERVER_IDENTIFIER
from homeassistant.const import CONF_HOST, CONF_PORT

Expand All @@ -17,6 +19,12 @@
},
]

MOCK_MONITORED_USERS = {
"a": {"enabled": True},
"b": {"enabled": False},
"c": {"enabled": True},
}


class MockResource:
"""Mock a PlexAccount resource."""
Expand Down Expand Up @@ -65,7 +73,14 @@ def __init__(self):
class MockPlexServer:
"""Mock a PlexServer instance."""

def __init__(self, index=0, ssl=True):
def __init__(
self,
index=0,
ssl=True,
load_users=True,
num_users=len(MOCK_MONITORED_USERS),
ignore_new_users=False,
):
"""Initialize the object."""
host = MOCK_SERVERS[index][CONF_HOST]
port = MOCK_SERVERS[index][CONF_PORT]
Expand All @@ -78,11 +93,24 @@ def __init__(self, index=0, ssl=True):
prefix = "https" if ssl else "http"
self._baseurl = f"{prefix}://{host}:{port}"
self._systemAccount = MockPlexSystemAccount()
self._ignore_new_users = ignore_new_users
self._load_users = load_users
self._num_users = num_users

def systemAccounts(self):
"""Mock the systemAccounts lookup method."""
return [self._systemAccount]

@property
def accounts(self):
"""Mock the accounts property."""
return set(["a", "b", "c"])

@property
def owner(self):
"""Mock the owner property."""
return "a"

@property
def url_in_use(self):
"""Return URL used by PlexServer."""
Expand All @@ -92,3 +120,24 @@ def url_in_use(self):
def version(self):
"""Mock version of PlexServer."""
return "1.0"

@property
def option_monitored_users(self):
"""Mock loaded config option for monitored users."""
userdict = dict(itertools.islice(MOCK_MONITORED_USERS.items(), self._num_users))
return userdict if self._load_users else {}

@property
def option_ignore_new_shared_users(self):
"""Mock loaded config option for ignoring new users."""
return self._ignore_new_users

@property
def option_show_all_controls(self):
"""Mock loaded config option for showing all controls."""
return False

@property
def option_use_episode_art(self):
"""Mock loaded config option for using episode art."""
return False
Loading