Merge pull request #1709 from croneter/py3-fix-dos
Fix Kodi getting blocked and losing PMS access e.g. due to cloudflare
This commit is contained in:
commit
e123c874c5
1 changed files with 36 additions and 12 deletions
|
@ -18,7 +18,11 @@ requests.packages.urllib3.disable_warnings()
|
||||||
# Timeout (connection timeout, read timeout)
|
# Timeout (connection timeout, read timeout)
|
||||||
# The later is up to 20 seconds, if the PMS has nothing to tell us
|
# The later is up to 20 seconds, if the PMS has nothing to tell us
|
||||||
# THIS WILL PREVENT PKC FROM SHUTTING DOWN CORRECTLY
|
# THIS WILL PREVENT PKC FROM SHUTTING DOWN CORRECTLY
|
||||||
TIMEOUT = (5.0, 3.0)
|
TIMEOUT = (5.0, 4.0)
|
||||||
|
|
||||||
|
# Max. timeout for the Listener: 2 ^ MAX_TIMEOUT
|
||||||
|
# Corresponds to 2 ^ 7 = 128 seconds
|
||||||
|
MAX_TIMEOUT = 7
|
||||||
|
|
||||||
log = getLogger('PLEX.companion.listener')
|
log = getLogger('PLEX.companion.listener')
|
||||||
|
|
||||||
|
@ -34,6 +38,7 @@ class Listener(backgroundthread.KillableThread):
|
||||||
def __init__(self, playstate_mgr):
|
def __init__(self, playstate_mgr):
|
||||||
self.s = None
|
self.s = None
|
||||||
self.playstate_mgr = playstate_mgr
|
self.playstate_mgr = playstate_mgr
|
||||||
|
self._sleep_timer = 0
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def _get_requests_session(self):
|
def _get_requests_session(self):
|
||||||
|
@ -56,6 +61,19 @@ class Listener(backgroundthread.KillableThread):
|
||||||
pass
|
pass
|
||||||
self.s = None
|
self.s = None
|
||||||
|
|
||||||
|
def _unauthorized(self):
|
||||||
|
"""Puts this thread to sleep until e.g. a PMS changes wakes it up"""
|
||||||
|
log.warn('We are not authorized to poll the PMS (http error 401). '
|
||||||
|
'Plex Companion will not work.')
|
||||||
|
self.suspend()
|
||||||
|
|
||||||
|
def _on_connection_error(self, req=None):
|
||||||
|
if req:
|
||||||
|
log_error(log.error, 'Error while contacting the PMS', req)
|
||||||
|
self.sleep(2 ^ self._sleep_timer)
|
||||||
|
if self._sleep_timer < MAX_TIMEOUT:
|
||||||
|
self._sleep_timer += 1
|
||||||
|
|
||||||
def ok_message(self, command_id):
|
def ok_message(self, command_id):
|
||||||
url = f'{app.CONN.server}/player/proxy/response?commandID={command_id}'
|
url = f'{app.CONN.server}/player/proxy/response?commandID={command_id}'
|
||||||
try:
|
try:
|
||||||
|
@ -129,28 +147,33 @@ class Listener(backgroundthread.KillableThread):
|
||||||
# No command received from the PMS - try again immediately
|
# No command received from the PMS - try again immediately
|
||||||
continue
|
continue
|
||||||
except requests.RequestException:
|
except requests.RequestException:
|
||||||
self.sleep(0.5)
|
self._on_connection_error()
|
||||||
continue
|
continue
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
# We need to quit PKC entirely
|
# We need to quit PKC entirely
|
||||||
break
|
break
|
||||||
|
|
||||||
# Sanity checks
|
# Sanity checks
|
||||||
if not req.ok:
|
if req.status_code == 401:
|
||||||
log_error(log.error, 'Error while contacting the PMS', req)
|
# We can't reach a PMS that is not in the local LAN
|
||||||
self.sleep(0.5)
|
# This might even lead to e.g. cloudflare blocking us, thinking
|
||||||
|
# we're staging a DOS attach
|
||||||
|
self._unauthorized()
|
||||||
continue
|
continue
|
||||||
|
elif not req.ok:
|
||||||
|
self._on_connection_error(req)
|
||||||
|
continue
|
||||||
|
elif not ('content-type' in req.headers
|
||||||
|
and 'xml' in req.headers['content-type']):
|
||||||
|
self._on_connection_error(req)
|
||||||
|
continue
|
||||||
|
|
||||||
if not req.text:
|
if not req.text:
|
||||||
# Means the connection timed-out (usually after 20 seconds),
|
# Means the connection timed-out (usually after 20 seconds),
|
||||||
# because there was no command from the PMS or a client to
|
# because there was no command from the PMS or a client to
|
||||||
# remote-control anything no the PKC-side
|
# remote-control anything no the PKC-side
|
||||||
# Received an empty body, but still header Content-Type: xml
|
# Received an empty body, but still header Content-Type: xml
|
||||||
continue
|
continue
|
||||||
if not ('content-type' in req.headers
|
|
||||||
and 'xml' in req.headers['content-type']):
|
|
||||||
log_error(log.error, 'Unexpected answer from the PMS', req)
|
|
||||||
self.sleep(0.5)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Parsing
|
# Parsing
|
||||||
try:
|
try:
|
||||||
|
@ -160,8 +183,8 @@ class Listener(backgroundthread.KillableThread):
|
||||||
# We should always just get ONE command per message
|
# We should always just get ONE command per message
|
||||||
raise IndexError()
|
raise IndexError()
|
||||||
except (utils.ParseError, IndexError):
|
except (utils.ParseError, IndexError):
|
||||||
log_error(log.error, 'Could not parse the PMS xml:', req)
|
log.error('Could not parse the PMS xml:')
|
||||||
self.sleep(0.5)
|
self._on_connection_error()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Do the work
|
# Do the work
|
||||||
|
@ -170,3 +193,4 @@ class Listener(backgroundthread.KillableThread):
|
||||||
self.playstate_mgr.check_subscriber(cmd)
|
self.playstate_mgr.check_subscriber(cmd)
|
||||||
if process_proxy_xml(cmd):
|
if process_proxy_xml(cmd):
|
||||||
self.ok_message(cmd.get('commandID'))
|
self.ok_message(cmd.get('commandID'))
|
||||||
|
self._sleep_timer = 0
|
||||||
|
|
Loading…
Reference in a new issue