Ps4 reformat media data (#25172)
* Reformat saved media data/ fix load + save helpers * Add url constant * Reformat saved media data * Add tests for media data * Refactor * Revert deleted lines * Set attrs after checking for lock * Patch load games. * remove unneeded imports * fix tests * Correct condition * Handle errors with loading games * Correct condition * Fix select source * add test * Remove unneeded vars * line break * cleanup loading json * remove test * move check for dict * Set games to {}pull/25332/head
parent
693fa15924
commit
48540fc21e
|
@ -1,20 +1,25 @@
|
|||
"""Support for PlayStation 4 consoles."""
|
||||
import logging
|
||||
import os
|
||||
|
||||
import voluptuous as vol
|
||||
from pyps4_homeassistant.ddp import async_create_ddp_endpoint
|
||||
from pyps4_homeassistant.media_art import COUNTRIES
|
||||
|
||||
from homeassistant.components.media_player.const import (
|
||||
ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_TITLE, MEDIA_TYPE_GAME)
|
||||
from homeassistant.const import (
|
||||
ATTR_COMMAND, ATTR_ENTITY_ID, CONF_REGION, CONF_TOKEN)
|
||||
ATTR_COMMAND, ATTR_ENTITY_ID, ATTR_LOCKED, CONF_REGION, CONF_TOKEN)
|
||||
from homeassistant.core import split_entity_id
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry, config_validation as cv
|
||||
from homeassistant.helpers.typing import HomeAssistantType
|
||||
from homeassistant.util import location
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
|
||||
from .config_flow import PlayStation4FlowHandler # noqa: pylint: disable=unused-import
|
||||
from .const import COMMANDS, DOMAIN, GAMES_FILE, PS4_DATA
|
||||
from .const import (
|
||||
ATTR_MEDIA_IMAGE_URL, COMMANDS, DOMAIN, GAMES_FILE, PS4_DATA)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -141,12 +146,22 @@ def load_games(hass: HomeAssistantType) -> dict:
|
|||
"""Load games for sources."""
|
||||
g_file = hass.config.path(GAMES_FILE)
|
||||
try:
|
||||
games = load_json(g_file)
|
||||
games = load_json(g_file, dict)
|
||||
except HomeAssistantError as error:
|
||||
games = {}
|
||||
_LOGGER.error("Failed to load games file: %s", error)
|
||||
|
||||
if not isinstance(games, dict):
|
||||
_LOGGER.error("Games file was not parsed correctly")
|
||||
games = {}
|
||||
|
||||
# If file does not exist, create empty file.
|
||||
except FileNotFoundError:
|
||||
if not os.path.isfile(g_file):
|
||||
_LOGGER.info("Creating PS4 Games File")
|
||||
games = {}
|
||||
save_games(hass, games)
|
||||
else:
|
||||
games = _reformat_data(hass, games)
|
||||
return games
|
||||
|
||||
|
||||
|
@ -158,9 +173,27 @@ def save_games(hass: HomeAssistantType, games: dict):
|
|||
except OSError as error:
|
||||
_LOGGER.error("Could not save game list, %s", error)
|
||||
|
||||
# Retry loading file
|
||||
if games is None:
|
||||
load_games(hass)
|
||||
|
||||
def _reformat_data(hass: HomeAssistantType, games: dict) -> dict:
|
||||
"""Reformat data to correct format."""
|
||||
data_reformatted = False
|
||||
|
||||
for game, data in games.items():
|
||||
# Convert str format to dict format.
|
||||
if not isinstance(data, dict):
|
||||
# Use existing title. Assign defaults.
|
||||
games[game] = {ATTR_LOCKED: False,
|
||||
ATTR_MEDIA_TITLE: data,
|
||||
ATTR_MEDIA_IMAGE_URL: None,
|
||||
ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_GAME}
|
||||
data_reformatted = True
|
||||
|
||||
_LOGGER.debug(
|
||||
"Reformatting media data for item: %s, %s", game, data)
|
||||
|
||||
if data_reformatted:
|
||||
save_games(hass, games)
|
||||
return games
|
||||
|
||||
|
||||
def service_handle(hass: HomeAssistantType):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Constants for PlayStation 4."""
|
||||
ATTR_MEDIA_IMAGE_URL = 'media_image_url'
|
||||
CONFIG_ENTRY_VERSION = 3
|
||||
DEFAULT_NAME = "PlayStation 4"
|
||||
DEFAULT_REGION = "United States"
|
||||
|
|
|
@ -9,17 +9,19 @@ from homeassistant.core import callback
|
|||
from homeassistant.components.media_player import (
|
||||
ENTITY_IMAGE_URL, MediaPlayerDevice)
|
||||
from homeassistant.components.media_player.const import (
|
||||
MEDIA_TYPE_GAME, MEDIA_TYPE_APP, SUPPORT_SELECT_SOURCE,
|
||||
SUPPORT_PAUSE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON)
|
||||
ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_TITLE,
|
||||
MEDIA_TYPE_GAME, MEDIA_TYPE_APP, SUPPORT_SELECT_SOURCE, SUPPORT_PAUSE,
|
||||
SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON)
|
||||
from homeassistant.components.ps4 import (
|
||||
format_unique_id, load_games, save_games)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_NAME, CONF_REGION,
|
||||
CONF_TOKEN, STATE_IDLE, STATE_OFF, STATE_PLAYING)
|
||||
ATTR_LOCKED, CONF_HOST, CONF_NAME, CONF_REGION, CONF_TOKEN,
|
||||
STATE_IDLE, STATE_OFF, STATE_PLAYING)
|
||||
from homeassistant.helpers import device_registry, entity_registry
|
||||
|
||||
from .const import (DEFAULT_ALIAS, DOMAIN as PS4_DOMAIN, PS4_DATA,
|
||||
REGIONS as deprecated_regions)
|
||||
from .const import (
|
||||
ATTR_MEDIA_IMAGE_URL, DEFAULT_ALIAS, DOMAIN as PS4_DOMAIN,
|
||||
PS4_DATA, REGIONS as deprecated_regions)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -147,20 +149,29 @@ class PS4Device(MediaPlayerDevice):
|
|||
|
||||
if status is not None:
|
||||
self._games = load_games(self.hass)
|
||||
if self._games is not None:
|
||||
self._source_list = list(sorted(self._games.values()))
|
||||
if self._games:
|
||||
self.get_source_list()
|
||||
|
||||
self._retry = 0
|
||||
self._disconnected = False
|
||||
if status.get('status') == 'Ok':
|
||||
title_id = status.get('running-app-titleid')
|
||||
name = status.get('running-app-name')
|
||||
|
||||
if title_id and name is not None:
|
||||
self._state = STATE_PLAYING
|
||||
|
||||
if self._media_content_id != title_id:
|
||||
self._media_content_id = title_id
|
||||
if self._use_saved():
|
||||
_LOGGER.debug(
|
||||
"Using saved data for media: %s", title_id)
|
||||
return
|
||||
|
||||
self._media_title = name
|
||||
self._source = self._media_title
|
||||
self._media_type = None
|
||||
# Get data from PS Store.
|
||||
asyncio.ensure_future(
|
||||
self.async_get_title_data(title_id, name))
|
||||
else:
|
||||
|
@ -175,6 +186,23 @@ class PS4Device(MediaPlayerDevice):
|
|||
else:
|
||||
self._retry += 1
|
||||
|
||||
def _use_saved(self) -> bool:
|
||||
"""Return True, Set media attrs if data is locked."""
|
||||
if self._media_content_id in self._games:
|
||||
store = self._games[self._media_content_id]
|
||||
|
||||
# If locked get attributes from file.
|
||||
locked = store.get(ATTR_LOCKED)
|
||||
if locked:
|
||||
self._media_title = store.get(ATTR_MEDIA_TITLE)
|
||||
self._source = self._media_title
|
||||
self._media_image = store.get(
|
||||
ATTR_MEDIA_IMAGE_URL)
|
||||
self._media_type = store.get(
|
||||
ATTR_MEDIA_CONTENT_TYPE)
|
||||
return True
|
||||
return False
|
||||
|
||||
def idle(self):
|
||||
"""Set states for state idle."""
|
||||
self.reset_title()
|
||||
|
@ -246,20 +274,33 @@ class PS4Device(MediaPlayerDevice):
|
|||
"""Update Game List, Correct data if different."""
|
||||
if self._media_content_id in self._games:
|
||||
store = self._games[self._media_content_id]
|
||||
if store != self._media_title:
|
||||
|
||||
if store.get(ATTR_MEDIA_TITLE) != self._media_title or\
|
||||
store.get(ATTR_MEDIA_IMAGE_URL) != self._media_image:
|
||||
self._games.pop(self._media_content_id)
|
||||
|
||||
if self._media_content_id not in self._games:
|
||||
self.add_games(self._media_content_id, self._media_title)
|
||||
self.add_games(
|
||||
self._media_content_id, self._media_title,
|
||||
self._media_image, self._media_type)
|
||||
self._games = load_games(self.hass)
|
||||
|
||||
self._source_list = list(sorted(self._games.values()))
|
||||
self.get_source_list()
|
||||
|
||||
def add_games(self, title_id, app_name):
|
||||
def get_source_list(self):
|
||||
"""Parse data entry and update source list."""
|
||||
games = []
|
||||
for data in self._games.values():
|
||||
games.append(data[ATTR_MEDIA_TITLE])
|
||||
self._source_list = sorted(games)
|
||||
|
||||
def add_games(self, title_id, app_name, image, g_type, is_locked=False):
|
||||
"""Add games to list."""
|
||||
games = self._games
|
||||
if title_id is not None and title_id not in games:
|
||||
game = {title_id: app_name}
|
||||
game = {title_id: {
|
||||
ATTR_MEDIA_TITLE: app_name, ATTR_MEDIA_IMAGE_URL: image,
|
||||
ATTR_MEDIA_CONTENT_TYPE: g_type, ATTR_LOCKED: is_locked}}
|
||||
games.update(game)
|
||||
save_games(self.hass, games)
|
||||
|
||||
|
@ -399,7 +440,8 @@ class PS4Device(MediaPlayerDevice):
|
|||
|
||||
async def async_select_source(self, source):
|
||||
"""Select input source."""
|
||||
for title_id, game in self._games.items():
|
||||
for title_id, data in self._games.items():
|
||||
game = data[ATTR_MEDIA_TITLE]
|
||||
if source.lower().encode(encoding='utf-8') == \
|
||||
game.lower().encode(encoding='utf-8') \
|
||||
or source == title_id:
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
"""Tests for the PS4 Integration."""
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.components import ps4
|
||||
from homeassistant.components.media_player.const import (
|
||||
ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_TITLE, MEDIA_TYPE_GAME)
|
||||
from homeassistant.components.ps4.const import (
|
||||
COMMANDS, CONFIG_ENTRY_VERSION as VERSION,
|
||||
ATTR_MEDIA_IMAGE_URL, COMMANDS, CONFIG_ENTRY_VERSION as VERSION,
|
||||
DEFAULT_REGION, DOMAIN, PS4_DATA)
|
||||
from homeassistant.const import (
|
||||
ATTR_COMMAND, ATTR_ENTITY_ID, CONF_HOST,
|
||||
ATTR_COMMAND, ATTR_ENTITY_ID, ATTR_LOCKED, CONF_HOST,
|
||||
CONF_NAME, CONF_REGION, CONF_TOKEN)
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.util import location
|
||||
from homeassistant.setup import async_setup_component
|
||||
from tests.common import (MockConfigEntry, mock_coro, mock_registry)
|
||||
|
@ -63,6 +66,29 @@ MOCK_ENTRY_VERSION_1 = MockConfigEntry(
|
|||
|
||||
MOCK_UNIQUE_ID = 'someuniqueid'
|
||||
|
||||
MOCK_ID = 'CUSA00123'
|
||||
MOCK_URL = 'http://someurl.jpeg'
|
||||
MOCK_TITLE = 'Some Title'
|
||||
MOCK_TYPE = MEDIA_TYPE_GAME
|
||||
|
||||
MOCK_GAMES_DATA_OLD_STR_FORMAT = {'mock_id': 'mock_title',
|
||||
'mock_id2': 'mock_title2'}
|
||||
|
||||
MOCK_GAMES_DATA = {
|
||||
ATTR_LOCKED: False,
|
||||
ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_GAME,
|
||||
ATTR_MEDIA_IMAGE_URL: MOCK_URL,
|
||||
ATTR_MEDIA_TITLE: MOCK_TITLE}
|
||||
|
||||
MOCK_GAMES_DATA_LOCKED = {
|
||||
ATTR_LOCKED: True,
|
||||
ATTR_MEDIA_CONTENT_TYPE: MEDIA_TYPE_GAME,
|
||||
ATTR_MEDIA_IMAGE_URL: MOCK_URL,
|
||||
ATTR_MEDIA_TITLE: MOCK_TITLE}
|
||||
|
||||
MOCK_GAMES = {MOCK_ID: MOCK_GAMES_DATA}
|
||||
MOCK_GAMES_LOCKED = {MOCK_ID: MOCK_GAMES_DATA_LOCKED}
|
||||
|
||||
|
||||
async def test_ps4_integration_setup(hass):
|
||||
"""Test PS4 integration is setup."""
|
||||
|
@ -147,6 +173,80 @@ async def setup_mock_component(hass):
|
|||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
def test_games_reformat_to_dict(hass):
|
||||
"""Test old data format is converted to new format."""
|
||||
with patch('homeassistant.components.ps4.load_json',
|
||||
return_value=MOCK_GAMES_DATA_OLD_STR_FORMAT),\
|
||||
patch('homeassistant.components.ps4.save_json',
|
||||
side_effect=MagicMock()),\
|
||||
patch('os.path.isfile', return_value=True):
|
||||
mock_games = ps4.load_games(hass)
|
||||
|
||||
# New format is a nested dict.
|
||||
assert isinstance(mock_games, dict)
|
||||
assert mock_games['mock_id'][ATTR_MEDIA_TITLE] == 'mock_title'
|
||||
assert mock_games['mock_id2'][ATTR_MEDIA_TITLE] == 'mock_title2'
|
||||
for mock_game in mock_games:
|
||||
mock_data = mock_games[mock_game]
|
||||
assert isinstance(mock_data, dict)
|
||||
assert mock_data
|
||||
assert mock_data[ATTR_MEDIA_IMAGE_URL] is None
|
||||
assert mock_data[ATTR_LOCKED] is False
|
||||
assert mock_data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_GAME
|
||||
|
||||
|
||||
def test_load_games(hass):
|
||||
"""Test that games are loaded correctly."""
|
||||
with patch('homeassistant.components.ps4.load_json',
|
||||
return_value=MOCK_GAMES),\
|
||||
patch('homeassistant.components.ps4.save_json',
|
||||
side_effect=MagicMock()),\
|
||||
patch('os.path.isfile', return_value=True):
|
||||
mock_games = ps4.load_games(hass)
|
||||
|
||||
assert isinstance(mock_games, dict)
|
||||
|
||||
mock_data = mock_games[MOCK_ID]
|
||||
assert isinstance(mock_data, dict)
|
||||
assert mock_data[ATTR_MEDIA_TITLE] == MOCK_TITLE
|
||||
assert mock_data[ATTR_MEDIA_IMAGE_URL] == MOCK_URL
|
||||
assert mock_data[ATTR_LOCKED] is False
|
||||
assert mock_data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_GAME
|
||||
|
||||
|
||||
def test_loading_games_returns_dict(hass):
|
||||
"""Test that loading games always returns a dict."""
|
||||
with patch('homeassistant.components.ps4.load_json',
|
||||
side_effect=HomeAssistantError),\
|
||||
patch('homeassistant.components.ps4.save_json',
|
||||
side_effect=MagicMock()),\
|
||||
patch('os.path.isfile', return_value=True):
|
||||
mock_games = ps4.load_games(hass)
|
||||
|
||||
assert isinstance(mock_games, dict)
|
||||
assert not mock_games
|
||||
|
||||
with patch('homeassistant.components.ps4.load_json',
|
||||
return_value='Some String'),\
|
||||
patch('homeassistant.components.ps4.save_json',
|
||||
side_effect=MagicMock()),\
|
||||
patch('os.path.isfile', return_value=True):
|
||||
mock_games = ps4.load_games(hass)
|
||||
|
||||
assert isinstance(mock_games, dict)
|
||||
assert not mock_games
|
||||
|
||||
with patch('homeassistant.components.ps4.load_json',
|
||||
return_value=[]),\
|
||||
patch('homeassistant.components.ps4.save_json',
|
||||
side_effect=MagicMock()),\
|
||||
patch('os.path.isfile', return_value=True):
|
||||
mock_games = ps4.load_games(hass)
|
||||
|
||||
assert isinstance(mock_games, dict)
|
||||
assert not mock_games
|
||||
|
||||
|
||||
async def test_send_command(hass):
|
||||
"""Test send_command service."""
|
||||
await setup_mock_component(hass)
|
||||
|
|
Loading…
Reference in New Issue