Standardize Plex server connections (#26444)

* Common connection class

* Omit tests for new Plex files

* Oops

* Add missing properties

* Remove redundant log message

* Stopgap to avoid duplicate setups

* Cleaner check for server setup

Co-Authored-By: Martin Hjelmare <marhje52@kth.se>

* Cleaner check for server setup

Co-Authored-By: Martin Hjelmare <marhje52@kth.se>

* Not needed with previous setup check

* Remove username/password support

* Reduce log level

Co-Authored-By: Martin Hjelmare <marhje52@kth.se>

* Don't do setup in __init__

* Oops

* Committing too fast...

* Connect after init

* Catch update exceptions like media_player

* Pass in validated PlexServer instance

* Remove unnecessary check

* Counter should be unknown on init

* Remove servername config option
This commit is contained in:
jjlawren 2019-09-05 12:50:26 -05:00 committed by Martin Hjelmare
parent a000125729
commit 2cd845fb25
5 changed files with 136 additions and 88 deletions

View File

@ -470,8 +470,7 @@ omit =
homeassistant/components/pioneer/media_player.py
homeassistant/components/pjlink/media_player.py
homeassistant/components/plaato/*
homeassistant/components/plex/media_player.py
homeassistant/components/plex/sensor.py
homeassistant/components/plex/*
homeassistant/components/plugwise/*
homeassistant/components/plum_lightpad/*
homeassistant/components/pocketcasts/sensor.py

View File

@ -0,0 +1,15 @@
"""Constants for the Plex component."""
DOMAIN = "plex"
NAME_FORMAT = "Plex {}"
DEFAULT_PORT = 32400
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = True
PLEX_CONFIG_FILE = "plex.conf"
PLEX_SERVER_CONFIG = "server_config"
CONF_USE_EPISODE_ART = "use_episode_art"
CONF_SHOW_ALL_CONTROLS = "show_all_controls"
CONF_REMOVE_UNAVAILABLE_CLIENTS = "remove_unavailable_clients"
CONF_CLIENT_REMOVE_INTERVAL = "client_remove_interval"

View File

@ -2,8 +2,7 @@
from datetime import timedelta
import json
import logging
import requests
import requests.exceptions
import voluptuous as vol
from homeassistant.components.media_player import MediaPlayerDevice, PLATFORM_SCHEMA
@ -21,6 +20,9 @@ from homeassistant.components.media_player.const import (
SUPPORT_VOLUME_SET,
)
from homeassistant.const import (
CONF_URL,
CONF_TOKEN,
CONF_VERIFY_SSL,
DEVICE_DEFAULT_NAME,
STATE_IDLE,
STATE_OFF,
@ -32,18 +34,22 @@ from homeassistant.helpers.event import track_time_interval
from homeassistant.util import dt as dt_util
from homeassistant.util.json import load_json, save_json
from .const import (
CONF_USE_EPISODE_ART,
CONF_SHOW_ALL_CONTROLS,
CONF_REMOVE_UNAVAILABLE_CLIENTS,
CONF_CLIENT_REMOVE_INTERVAL,
DOMAIN as PLEX_DOMAIN,
NAME_FORMAT,
PLEX_CONFIG_FILE,
)
from .server import PlexServer
SERVER_SETUP = "server_setup"
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
NAME_FORMAT = "Plex {}"
PLEX_CONFIG_FILE = "plex.conf"
PLEX_DATA = "plex"
CONF_USE_EPISODE_ART = "use_episode_art"
CONF_SHOW_ALL_CONTROLS = "show_all_controls"
CONF_REMOVE_UNAVAILABLE_CLIENTS = "remove_unavailable_clients"
CONF_CLIENT_REMOVE_INTERVAL = "client_remove_interval"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_USE_EPISODE_ART, default=False): cv.boolean,
@ -58,8 +64,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
def setup_platform(hass, config, add_entities_callback, discovery_info=None):
"""Set up the Plex platform."""
if PLEX_DATA not in hass.data:
hass.data[PLEX_DATA] = {}
plex_data = hass.data.setdefault(PLEX_DOMAIN, {})
server_setup = plex_data.setdefault(SERVER_SETUP, False)
if server_setup:
return
# get config from plex.conf
file_config = load_json(hass.config.path(PLEX_CONFIG_FILE))
@ -102,20 +110,19 @@ def setup_plexserver(
host, token, has_ssl, verify_ssl, hass, config, add_entities_callback
):
"""Set up a plexserver based on host parameter."""
import plexapi.server
import plexapi.exceptions
cert_session = None
http_prefix = "https" if has_ssl else "http"
if has_ssl and (verify_ssl is False):
_LOGGER.info("Ignoring SSL verification")
cert_session = requests.Session()
cert_session.verify = False
server_config = {
CONF_URL: f"{http_prefix}://{host}",
CONF_TOKEN: token,
CONF_VERIFY_SSL: verify_ssl,
}
try:
plexserver = plexapi.server.PlexServer(
f"{http_prefix}://{host}", token, cert_session
)
_LOGGER.info("Discovery configuration done (no token needed)")
plexserver = PlexServer(server_config)
plexserver.connect()
except (
plexapi.exceptions.BadRequest,
plexapi.exceptions.Unauthorized,
@ -125,6 +132,8 @@ def setup_plexserver(
# No token or wrong token
request_configuration(host, hass, config, add_entities_callback)
return
else:
hass.data[PLEX_DOMAIN][SERVER_SETUP] = True
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
@ -139,9 +148,7 @@ def setup_plexserver(
{host: {"token": token, "ssl": has_ssl, "verify": verify_ssl}},
)
_LOGGER.info("Connected to: %s://%s", http_prefix, host)
plex_clients = hass.data[PLEX_DATA]
plex_clients = {}
plex_sessions = {}
track_time_interval(hass, lambda now: update_devices(), timedelta(seconds=10))

View File

@ -1,32 +1,30 @@
"""Support for Plex media server monitoring."""
from datetime import timedelta
import logging
import plexapi.exceptions
import requests.exceptions
import voluptuous as vol
from homeassistant.components.switch import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_NAME,
CONF_USERNAME,
CONF_PASSWORD,
CONF_HOST,
CONF_PORT,
CONF_TOKEN,
CONF_SSL,
CONF_URL,
CONF_VERIFY_SSL,
)
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_SERVER = "server"
from .const import DEFAULT_PORT, DEFAULT_SSL, DEFAULT_VERIFY_SSL
from .server import PlexServer
DEFAULT_HOST = "localhost"
DEFAULT_NAME = "Plex"
DEFAULT_PORT = 32400
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = True
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
@ -34,11 +32,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_TOKEN): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_SERVER): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
}
@ -48,34 +43,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Plex sensor."""
name = config.get(CONF_NAME)
plex_user = config.get(CONF_USERNAME)
plex_password = config.get(CONF_PASSWORD)
plex_server = config.get(CONF_SERVER)
plex_host = config.get(CONF_HOST)
plex_port = config.get(CONF_PORT)
plex_token = config.get(CONF_TOKEN)
verify_ssl = config.get(CONF_VERIFY_SSL)
plex_url = "{}://{}:{}".format(
"https" if config.get(CONF_SSL) else "http", plex_host, plex_port
)
import plexapi.exceptions
try:
add_entities(
[
PlexSensor(
name,
plex_url,
plex_user,
plex_password,
plex_server,
plex_token,
config.get(CONF_VERIFY_SSL),
)
],
True,
plex_server = PlexServer(
{CONF_URL: plex_url, CONF_TOKEN: plex_token, CONF_VERIFY_SSL: verify_ssl}
)
plex_server.connect()
except (
plexapi.exceptions.BadRequest,
plexapi.exceptions.Unauthorized,
@ -84,43 +65,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
_LOGGER.error(error)
return
add_entities([PlexSensor(name, plex_server)], True)
class PlexSensor(Entity):
"""Representation of a Plex now playing sensor."""
def __init__(
self,
name,
plex_url,
plex_user,
plex_password,
plex_server,
plex_token,
verify_ssl,
):
def __init__(self, name, plex_server):
"""Initialize the sensor."""
from plexapi.myplex import MyPlexAccount
from plexapi.server import PlexServer
from requests import Session
self._name = name
self._state = 0
self._state = None
self._now_playing = []
cert_session = None
if not verify_ssl:
_LOGGER.info("Ignoring SSL verification")
cert_session = Session()
cert_session.verify = False
if plex_token:
self._server = PlexServer(plex_url, plex_token, cert_session)
elif plex_user and plex_password:
user = MyPlexAccount(plex_user, plex_password)
server = plex_server if plex_server else user.resources()[0].name
self._server = user.resource(server).connect()
else:
self._server = PlexServer(plex_url, None, cert_session)
self._server = plex_server
@property
def name(self):
@ -145,7 +101,19 @@ class PlexSensor(Entity):
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update method for Plex sensor."""
sessions = self._server.sessions()
try:
sessions = self._server.sessions()
except plexapi.exceptions.BadRequest:
_LOGGER.error(
"Error listing current Plex sessions on %s", self._server.friendly_name
)
return
except requests.exceptions.RequestException as ex:
_LOGGER.warning(
"Temporary error connecting to %s (%s)", self._server.friendly_name, ex
)
return
now_playing = []
for sess in sessions:
user = sess.usernames[0]

View File

@ -0,0 +1,59 @@
"""Shared class to maintain Plex server instances."""
import logging
import plexapi.server
from requests import Session
from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL
from .const import DEFAULT_VERIFY_SSL
_LOGGER = logging.getLogger(__package__)
class PlexServer:
"""Manages a single Plex server connection."""
def __init__(self, server_config):
"""Initialize a Plex server instance."""
self._plex_server = None
self._url = server_config.get(CONF_URL)
self._token = server_config.get(CONF_TOKEN)
self._verify_ssl = server_config.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL)
def connect(self):
"""Connect to a Plex server directly, obtaining direct URL if necessary."""
def _connect_with_url():
session = None
if self._url.startswith("https") and not self._verify_ssl:
session = Session()
session.verify = False
self._plex_server = plexapi.server.PlexServer(
self._url, self._token, session
)
_LOGGER.debug("Connected to: %s (%s)", self.friendly_name, self.url_in_use)
_connect_with_url()
def clients(self):
"""Pass through clients call to plexapi."""
return self._plex_server.clients()
def sessions(self):
"""Pass through sessions call to plexapi."""
return self._plex_server.sessions()
@property
def friendly_name(self):
"""Return name of connected Plex server."""
return self._plex_server.friendlyName
@property
def machine_identifier(self):
"""Return unique identifier of connected Plex server."""
return self._plex_server.machineIdentifier
@property
def url_in_use(self):
"""Return URL used for connected Plex server."""
return self._plex_server._baseurl # pylint: disable=W0212