Stop using entity_picture that is known to be bad. (#5856)
* Stop using entity_picture that is known to be bad. * Only abandon image on 400 or 404 response * Return is_permanent_failure as a third part of response * Add debug printout * Fix lint * Fix lint
This commit is contained in:
parent
2711c12928
commit
b981bfba7e
2 changed files with 83 additions and 11 deletions
|
@ -391,6 +391,7 @@ class MediaPlayerDevice(Entity):
|
||||||
"""ABC for media player devices."""
|
"""ABC for media player devices."""
|
||||||
|
|
||||||
_access_token = None
|
_access_token = None
|
||||||
|
_last_bad_image_url = None
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
# Implement these for your media player
|
# Implement these for your media player
|
||||||
|
@ -809,8 +810,7 @@ class MediaPlayerDevice(Entity):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
url = self.media_image_url
|
url = self.media_image_url
|
||||||
|
if url in (None, self._last_bad_image_url):
|
||||||
if url is None:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return ENTITY_IMAGE_URL.format(
|
return ENTITY_IMAGE_URL.format(
|
||||||
|
@ -836,6 +836,14 @@ class MediaPlayerDevice(Entity):
|
||||||
_async_fetch_image(self.hass, url), self.hass.loop
|
_async_fetch_image(self.hass, url), self.hass.loop
|
||||||
).result()
|
).result()
|
||||||
|
|
||||||
|
def set_last_bad_image_url(self, url):
|
||||||
|
"""Save last bad image url."""
|
||||||
|
should_update = self._last_bad_image_url != url
|
||||||
|
self._last_bad_image_url = url
|
||||||
|
if should_update:
|
||||||
|
_LOGGER.debug('%s marked as inaccessible', url)
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def _async_fetch_image(hass, url):
|
def _async_fetch_image(hass, url):
|
||||||
|
@ -846,11 +854,10 @@ def _async_fetch_image(hass, url):
|
||||||
cache_images = ENTITY_IMAGE_CACHE[ATTR_CACHE_IMAGES]
|
cache_images = ENTITY_IMAGE_CACHE[ATTR_CACHE_IMAGES]
|
||||||
cache_urls = ENTITY_IMAGE_CACHE[ATTR_CACHE_URLS]
|
cache_urls = ENTITY_IMAGE_CACHE[ATTR_CACHE_URLS]
|
||||||
cache_maxsize = ENTITY_IMAGE_CACHE[ATTR_CACHE_MAXSIZE]
|
cache_maxsize = ENTITY_IMAGE_CACHE[ATTR_CACHE_MAXSIZE]
|
||||||
|
|
||||||
if url in cache_images:
|
if url in cache_images:
|
||||||
return cache_images[url]
|
return cache_images[url]
|
||||||
|
|
||||||
content, content_type = (None, None)
|
content, content_type, is_permanent_failure = (None, None, False)
|
||||||
websession = async_get_clientsession(hass)
|
websession = async_get_clientsession(hass)
|
||||||
response = None
|
response = None
|
||||||
try:
|
try:
|
||||||
|
@ -859,6 +866,8 @@ def _async_fetch_image(hass, url):
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
content = yield from response.read()
|
content = yield from response.read()
|
||||||
content_type = response.headers.get(CONTENT_TYPE_HEADER)
|
content_type = response.headers.get(CONTENT_TYPE_HEADER)
|
||||||
|
elif response.status in (400, 404):
|
||||||
|
is_permanent_failure = True
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
pass
|
pass
|
||||||
|
@ -868,9 +877,9 @@ def _async_fetch_image(hass, url):
|
||||||
yield from response.release()
|
yield from response.release()
|
||||||
|
|
||||||
if not content:
|
if not content:
|
||||||
return (None, None)
|
return (None, None, is_permanent_failure)
|
||||||
|
|
||||||
cache_images[url] = (content, content_type)
|
cache_images[url] = (content, content_type, is_permanent_failure)
|
||||||
cache_urls.append(url)
|
cache_urls.append(url)
|
||||||
|
|
||||||
while len(cache_urls) > cache_maxsize:
|
while len(cache_urls) > cache_maxsize:
|
||||||
|
@ -881,7 +890,7 @@ def _async_fetch_image(hass, url):
|
||||||
|
|
||||||
cache_urls = cache_urls[1:]
|
cache_urls = cache_urls[1:]
|
||||||
|
|
||||||
return content, content_type
|
return content, content_type, is_permanent_failure
|
||||||
|
|
||||||
|
|
||||||
class MediaPlayerImageView(HomeAssistantView):
|
class MediaPlayerImageView(HomeAssistantView):
|
||||||
|
@ -907,12 +916,15 @@ class MediaPlayerImageView(HomeAssistantView):
|
||||||
request.GET.get('token') == player.access_token)
|
request.GET.get('token') == player.access_token)
|
||||||
|
|
||||||
if not authenticated:
|
if not authenticated:
|
||||||
return web.Response(status=401)
|
return web.Response(status=401),
|
||||||
|
|
||||||
data, content_type = yield from _async_fetch_image(
|
url = player.media_image_url
|
||||||
request.app['hass'], player.media_image_url)
|
data, content_type, is_permanent_failure = \
|
||||||
|
yield from _async_fetch_image(request.app['hass'], url)
|
||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
|
if is_permanent_failure:
|
||||||
|
player.set_last_bad_image_url(url)
|
||||||
return web.Response(status=500)
|
return web.Response(status=500)
|
||||||
|
|
||||||
return web.Response(body=data, content_type=content_type)
|
return web.Response(body=data, content_type=content_type)
|
||||||
|
|
|
@ -254,7 +254,7 @@ class TestMediaPlayerWeb(unittest.TestCase):
|
||||||
assert setup_component(
|
assert setup_component(
|
||||||
self.hass, mp.DOMAIN,
|
self.hass, mp.DOMAIN,
|
||||||
{'media_player': {'platform': 'demo'}})
|
{'media_player': {'platform': 'demo'}})
|
||||||
|
mp.ENTITY_IMAGE_CACHE[mp.ATTR_CACHE_IMAGES].clear()
|
||||||
self.hass.start()
|
self.hass.start()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -295,3 +295,63 @@ class TestMediaPlayerWeb(unittest.TestCase):
|
||||||
state.attributes.get('entity_picture'))
|
state.attributes.get('entity_picture'))
|
||||||
assert req.status_code == 200
|
assert req.status_code == 200
|
||||||
assert req.text == fake_picture_data
|
assert req.text == fake_picture_data
|
||||||
|
|
||||||
|
def test_media_image_proxy_500(self):
|
||||||
|
"""Test the media server image proxy server ."""
|
||||||
|
class MockResponse():
|
||||||
|
def __init__(self):
|
||||||
|
self.status = 500
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def release(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MockWebsession():
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def get(self, url):
|
||||||
|
return MockResponse()
|
||||||
|
|
||||||
|
def detach(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.hass.data[DATA_CLIENTSESSION] = MockWebsession()
|
||||||
|
|
||||||
|
assert self.hass.states.is_state(entity_id, 'playing')
|
||||||
|
state = self.hass.states.get(entity_id)
|
||||||
|
req = requests.get(HTTP_BASE_URL +
|
||||||
|
state.attributes.get('entity_picture'))
|
||||||
|
assert req.status_code == 500
|
||||||
|
self.hass.block_till_done()
|
||||||
|
assert self.hass.states.get(entity_id).attributes.get(
|
||||||
|
'entity_picture') is not None
|
||||||
|
|
||||||
|
def test_media_image_proxy_404(self):
|
||||||
|
"""Test the media server image proxy server ."""
|
||||||
|
class MockResponse():
|
||||||
|
def __init__(self):
|
||||||
|
self.status = 404
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def release(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MockWebsession():
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def get(self, url):
|
||||||
|
return MockResponse()
|
||||||
|
|
||||||
|
def detach(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.hass.data[DATA_CLIENTSESSION] = MockWebsession()
|
||||||
|
|
||||||
|
assert self.hass.states.is_state(entity_id, 'playing')
|
||||||
|
state = self.hass.states.get(entity_id)
|
||||||
|
req = requests.get(HTTP_BASE_URL +
|
||||||
|
state.attributes.get('entity_picture'))
|
||||||
|
assert req.status_code == 500
|
||||||
|
self.hass.block_till_done()
|
||||||
|
assert self.hass.states.get(entity_id).attributes.get(
|
||||||
|
'entity_picture') is None
|
||||||
|
|
Loading…
Add table
Reference in a new issue