python-plexapi/plexapi/alert.py

98 lines
4 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
2017-02-20 05:37:00 +00:00
import json
import socket
from typing import Callable
2017-02-20 05:37:00 +00:00
import threading
2020-04-27 19:12:18 +00:00
from plexapi import log
class AlertListener(threading.Thread):
2019-12-04 19:15:51 +00:00
""" Creates a websocket connection to the PlexServer to optionally receive alert notifications.
These often include messages from Plex about media scans as well as updates to currently running
2019-12-04 19:15:51 +00:00
Transcode Sessions. This class implements threading.Thread, therefore to start monitoring
alerts you must call .start() on the object once it's created. When calling
`PlexServer.startAlertListener()`, the thread will be started for you.
2017-02-24 16:18:54 +00:00
Improvements in tests process (#297) * lets begin * skip plexpass tests if there is not plexpass on account * test new myplex attrubutes * bootstrap: proper photos organisation * fix rest of photos tests * fix myplex new attributes test * fix music bootstrap by setting agent to lastfm * fix sync tests * increase bootstrap timeout * remove timeout from .travis.yml * do not create playlist-style photoalbums in plex-bootstraptest.py * allow negative filtering in LibrarySection.search() * fix sync tests once again * use sendCrashReports in test_settings * fix test_settings * fix test_video * do not accept eula in bootstrap * fix PlexServer.isLatest() * add test against old version of PlexServer * fix MyPlexAccount.OutOut * add flag for one-time testing in Travis * fix test_library onDeck tests * fix more tests * use tqdm in plex-bootstraptest for media scanning progress * create sections one-by-one * update docs on AlertListener for timeline entries * fix plex-bootstraptest for server version 1.3.2 * display skip/xpass/xfail reasons * fix tests on 1.3 * wait for music to be fully processed in plex-bootstraptest * fix misplaced TEST_ACCOUNT_ONCE * fix test_myplex_users, not sure if in proper-way * add pytest-rerunfailures; mark test_myplex_optout as flaky * fix comment * Revert "add pytest-rerunfailures; mark test_myplex_optout as flaky" This reverts commit 580e4c95a758c92329d757eb2f3fc3bf44b26f09. * restart plex container on failure * add conftest.wait_until() and used where some retries are required * add more wait_until() usage in test_sync * fix managed user search * fix updating managed users in myplex * allow to add new servers to existent users * add new server to a shared user while bootstrapping * add some docs on testing process * perform few attemps when unable to get the claim token * unlock websocket-client in requirements_dev * fix docblock in tools/plex-teardowntest * do not hardcode mediapart size in test_video * remove cache:pip from travis * Revert "unlock websocket-client in requirements_dev" This reverts commit 0d536bd06dbdc4a4b869a1686f8cd008898859fe. * remove debug from server.py * improve webhook tests * fix type() check to isinstance() * remove excessive `else` branch due to Hellowlol advice * add `unknown` as allowed `myPlexMappingState` in test_server
2018-09-14 18:03:23 +00:00
Known `state`-values for timeline entries, with identifier=`com.plexapp.plugins.library`:
:0: The item was created
:1: Reporting progress on item processing
:2: Matching the item
:3: Downloading the metadata
:4: Processing downloaded metadata
:5: The item processed
:9: The item deleted
When metadata agent is not set for the library processing ends with state=1.
Parameters:
server (:class:`~plexapi.server.PlexServer`): PlexServer this listener is connected to.
2019-12-04 19:15:51 +00:00
callback (func): Callback function to call on received messages. The callback function
2017-02-24 16:18:54 +00:00
will be sent a single argument 'data' which will contain a dictionary of data
2019-12-04 19:15:51 +00:00
received from the server. :samp:`def my_callback(data): ...`
callbackError (func): Callback function to call on errors. The callback function
will be sent a single argument 'error' which will contain the Error object.
:samp:`def my_callback(error): ...`
ws_socket (socket): Socket to use for the connection. If not specified, a new socket will be created.
"""
key = '/:/websockets/notifications'
def __init__(self, server, callback: Callable = None, callbackError: Callable = None, ws_socket: socket = None):
super(AlertListener, self).__init__()
self.daemon = True
self._server = server
self._callback = callback
self._callbackError = callbackError
self._socket = ws_socket
self._ws = None
def run(self):
2020-04-27 19:12:18 +00:00
try:
import websocket
except ImportError:
log.warning("Can't use the AlertListener without websocket")
return
# create the websocket connection
url = self._server.url(self.key, includeToken=True).replace('http', 'ws')
log.info('Starting AlertListener: %s', url)
self._ws = websocket.WebSocketApp(url, on_message=self._onMessage, on_error=self._onError, socket=self._socket)
self._ws.run_forever()
def stop(self):
2019-12-04 19:15:51 +00:00
""" Stop the AlertListener thread. Once the notifier is stopped, it cannot be directly
2020-11-23 03:06:30 +00:00
started again. You must call :func:`~plexapi.server.PlexServer.startAlertListener`
2017-02-24 16:18:54 +00:00
from a PlexServer instance.
"""
log.info('Stopping AlertListener.')
self._ws.close()
2019-12-04 18:53:30 +00:00
def _onMessage(self, *args):
2019-12-04 19:15:51 +00:00
""" Called when websocket message is received.
We are assuming the last argument in the tuple is the message.
"""
2019-12-04 18:53:30 +00:00
message = args[-1]
try:
data = json.loads(message)['NotificationContainer']
2017-10-25 22:51:25 +00:00
log.debug('Alert: %s %s %s', *data)
if self._callback:
self._callback(data)
2017-10-26 18:22:35 +00:00
except Exception as err: # pragma: no cover
log.error('AlertListener Msg Error: %s', err)
2019-12-04 18:53:30 +00:00
def _onError(self, *args): # pragma: no cover
2019-12-04 19:15:51 +00:00
""" Called when websocket error is received.
We are assuming the last argument in the tuple is the message.
"""
2019-12-04 18:53:30 +00:00
err = args[-1]
try:
log.error('AlertListener Error: %s', err)
if self._callbackError:
self._callbackError(err)
except Exception as err: # pragma: no cover
log.error('AlertListener Error: Error: %s', err)