Skip to content

Commit

Permalink
Provide data in callbacks
Browse files Browse the repository at this point in the history
Differentiate between websocket state changes and data payloads
Provide error reasons during unexpected disconnections
Specifically handle authorization rejections
  • Loading branch information
jjlawren committed Sep 30, 2020
1 parent cdc2ea3 commit bfb2096
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 5 deletions.
34 changes: 30 additions & 4 deletions plexwebsocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@

_LOGGER = logging.getLogger(__name__)

SIGNAL_DATA = "data"
SIGNAL_CONNECTION_STATE = "state"

ERROR_AUTH_FAILURE = "Authorization failure"
ERROR_TOO_MANY_RETRIES = "Too many retries"
ERROR_UNKNOWN = "Unknown"

MAX_FAILED_ATTEMPTS = 5

STATE_CONNECTED = "connected"
STATE_DISCONNECTED = "disconnected"
STATE_STARTING = "starting"
Expand Down Expand Up @@ -47,7 +56,10 @@ def __init__(self, plex_server, callback, session=None, verify_ssl=True):
plex_server (plexapi.server.PlexServer):
A connected PlexServer instance.
callback (Runnable):
Callback to issue when Plex player events occur.
Called when interesting events occur. Provides arguments:
signal (str): One of SIGNAL_* constants
data (str): Current STATE_* or websocket payload contents
error (str): Error message if any or None
verify_ssl:
Set to False to disable SSL certificate validation.
session (aiohttp.ClientSession, optional):
Expand All @@ -61,6 +73,7 @@ def __init__(self, plex_server, callback, session=None, verify_ssl=True):
self._ssl = False if verify_ssl is False else None
self._state = None
self.failed_attempts = 0
self._error_reason = None

@property
def state(self):
Expand All @@ -72,6 +85,8 @@ def state(self, value):
"""Set the state."""
self._state = value
_LOGGER.debug("Websocket %s", value)
self.callback(SIGNAL_CONNECTION_STATE, value, self._error_reason)
self._error_reason = None

@staticmethod
def _get_uri(plex_server):
Expand All @@ -90,7 +105,6 @@ async def running(self):
) as ws_client:
self.state = STATE_CONNECTED
self.failed_attempts = 0
self.callback()

async for message in ws_client:
if self.state == STATE_STOPPED:
Expand All @@ -99,7 +113,7 @@ async def running(self):
if message.type == aiohttp.WSMsgType.TEXT:
msg = message.json()["NotificationContainer"]
if self.player_event(msg):
self.callback()
self.callback(SIGNAL_DATA, msg, None)

elif message.type == aiohttp.WSMsgType.CLOSED:
_LOGGER.warning("AIOHTTP websocket connection closed")
Expand All @@ -109,8 +123,19 @@ async def running(self):
_LOGGER.error("AIOHTTP websocket error")
break

except aiohttp.ClientResponseError as error:
if error.code == 401:
_LOGGER.error("Credentials rejected: %s", error)
self._error_reason = ERROR_AUTH_FAILURE
else:
_LOGGER.error("Unexpected response received: %s", error)
self._error_reason = ERROR_UNKNOWN
self.state = STATE_STOPPED
except (aiohttp.ClientConnectionError, asyncio.TimeoutError) as error:
if self.state != STATE_STOPPED:
if self.failed_attempts >= MAX_FAILED_ATTEMPTS:
self._error_reason = ERROR_TOO_MANY_RETRIES
self.state = STATE_STOPPED
elif self.state != STATE_STOPPED:
retry_delay = min(2 ** (self.failed_attempts - 1) * 30, 300)
self.failed_attempts += 1
_LOGGER.error(
Expand All @@ -123,6 +148,7 @@ async def running(self):
except Exception as error: # pylint: disable=broad-except
if self.state != STATE_STOPPED:
_LOGGER.exception("Unexpected exception occurred: %s", error)
self._error_reason = ERROR_UNKNOWN
self.state = STATE_STOPPED
else:
if self.state != STATE_STOPPED:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
with open('README.md') as f:
long_description = f.read()

VERSION="0.0.11"
VERSION="0.0.12"

setup(
name='plexwebsocket',
Expand Down

0 comments on commit bfb2096

Please sign in to comment.