diff --git a/homeassistant/components/plex/media_browser.py b/homeassistant/components/plex/media_browser.py index ac316edb938..e8d5d32c66e 100644 --- a/homeassistant/components/plex/media_browser.py +++ b/homeassistant/components/plex/media_browser.py @@ -13,6 +13,10 @@ PLAYLISTS_BROWSE_PAYLOAD = { "can_play": False, "can_expand": True, } +SPECIAL_METHODS = { + "On Deck": "onDeck", + "Recently Added": "recentlyAdded", +} _LOGGER = logging.getLogger(__name__) @@ -36,14 +40,43 @@ def browse_media( media_info["children"].append(item_payload(item)) return media_info + if media_content_id and ":" in media_content_id: + media_content_id, special_folder = media_content_id.split(":") + else: + special_folder = None + if ( - media_content_type == "server" + media_content_type + and media_content_type == "server" and media_content_id != plex_server.machine_identifier ): raise BrowseError( f"Plex server with ID '{media_content_id}' is not associated with {entity_id}" ) + if special_folder: + if media_content_type == "server": + library_or_section = plex_server.library + title = plex_server.friendly_name + elif media_content_type == "library": + library_or_section = plex_server.library.sectionByID(media_content_id) + title = library_or_section.title + + payload = { + "title": title, + "media_content_id": f"{media_content_id}:{special_folder}", + "media_content_type": media_content_type, + "can_play": False, + "can_expand": True, + "children": [], + } + + method = SPECIAL_METHODS[special_folder] + items = getattr(library_or_section, method)() + for item in items: + payload["children"].append(item_payload(item)) + return payload + if media_content_type in ["server", None]: return server_payload(plex_server) @@ -89,6 +122,18 @@ def library_section_payload(section): } +def special_library_payload(parent_payload, special_type): + """Create response payload for special library folders.""" + title = f"{special_type} ({parent_payload['title']})" + return { + "title": title, + "media_content_id": f"{parent_payload['media_content_id']}:{special_type}", + "media_content_type": parent_payload["media_content_type"], + "can_play": False, + "can_expand": True, + } + + def server_payload(plex_server): """Create response payload to describe libraries of the Plex server.""" server_info = { @@ -99,6 +144,10 @@ def server_payload(plex_server): "can_expand": True, } server_info["children"] = [] + server_info["children"].append(special_library_payload(server_info, "On Deck")) + server_info["children"].append( + special_library_payload(server_info, "Recently Added") + ) for library in plex_server.library.sections(): if library.type == "photo": continue @@ -112,6 +161,10 @@ def library_payload(plex_server, library_id): library = plex_server.library.sectionByID(library_id) library_info = library_section_payload(library) library_info["children"] = [] + library_info["children"].append(special_library_payload(library_info, "On Deck")) + library_info["children"].append( + special_library_payload(library_info, "Recently Added") + ) for item in library.all(): library_info["children"].append(item_payload(item)) return library_info diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py index 287712cf520..1fc705be1ca 100644 --- a/tests/components/plex/mock_classes.py +++ b/tests/components/plex/mock_classes.py @@ -346,6 +346,14 @@ class MockPlexLibrary: """Mock the sectionByID lookup.""" return [x for x in self.sections() if x.key == section_id][0] + def onDeck(self): + """Mock an empty On Deck folder.""" + return [] + + def recentlyAdded(self): + """Mock an empty Recently Added folder.""" + return [] + class MockPlexLibrarySection: """Mock a Plex LibrarySection instance.""" @@ -381,6 +389,14 @@ class MockPlexLibrarySection: if child.ratingKey == ratingKey: return child + def onDeck(self): + """Mock an empty On Deck folder.""" + return [] + + def recentlyAdded(self): + """Mock an empty Recently Added folder.""" + return self.all() + @property def type(self): """Mock the library type.""" diff --git a/tests/components/plex/test_browse_media.py b/tests/components/plex/test_browse_media.py index 005fe4c9e44..9cf6d7a7332 100644 --- a/tests/components/plex/test_browse_media.py +++ b/tests/components/plex/test_browse_media.py @@ -4,6 +4,7 @@ from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_TYPE, ) from homeassistant.components.plex.const import CONF_SERVER_IDENTIFIER, DOMAIN +from homeassistant.components.plex.media_browser import SPECIAL_METHODS from homeassistant.components.websocket_api.const import ERR_UNKNOWN_ERROR, TYPE_RESULT from .const import DEFAULT_DATA, DEFAULT_OPTIONS @@ -77,10 +78,61 @@ async def test_browse_media(hass, hass_ws_client): result = msg["result"] assert result[ATTR_MEDIA_CONTENT_TYPE] == "server" assert result[ATTR_MEDIA_CONTENT_ID] == DEFAULT_DATA[CONF_SERVER_IDENTIFIER] - assert len(result["children"]) == len(mock_plex_server.library.sections()) + assert len(result["children"]) == len(mock_plex_server.library.sections()) + len( + SPECIAL_METHODS + ) tvshows = next(iter(x for x in result["children"] if x["title"] == "TV Shows")) playlists = next(iter(x for x in result["children"] if x["title"] == "Playlists")) + special_keys = list(SPECIAL_METHODS.keys()) + + # Browse into a special folder (server) + msg_id += 1 + await websocket_client.send_json( + { + "id": msg_id, + "type": "media_player/browse_media", + "entity_id": media_players[0], + ATTR_MEDIA_CONTENT_TYPE: "server", + ATTR_MEDIA_CONTENT_ID: f"{DEFAULT_DATA[CONF_SERVER_IDENTIFIER]}:{special_keys[0]}", + } + ) + + msg = await websocket_client.receive_json() + assert msg["id"] == msg_id + assert msg["type"] == TYPE_RESULT + assert msg["success"] + result = msg["result"] + assert result[ATTR_MEDIA_CONTENT_TYPE] == "server" + assert ( + result[ATTR_MEDIA_CONTENT_ID] + == f"{DEFAULT_DATA[CONF_SERVER_IDENTIFIER]}:{special_keys[0]}" + ) + assert len(result["children"]) == len(mock_plex_server.library.onDeck()) + + # Browse into a special folder (library) + msg_id += 1 + library_section_id = next(iter(mock_plex_server.library.sections())).key + await websocket_client.send_json( + { + "id": msg_id, + "type": "media_player/browse_media", + "entity_id": media_players[0], + ATTR_MEDIA_CONTENT_TYPE: "library", + ATTR_MEDIA_CONTENT_ID: f"{library_section_id}:{special_keys[1]}", + } + ) + + msg = await websocket_client.receive_json() + assert msg["id"] == msg_id + assert msg["type"] == TYPE_RESULT + assert msg["success"] + result = msg["result"] + assert result[ATTR_MEDIA_CONTENT_TYPE] == "library" + assert result[ATTR_MEDIA_CONTENT_ID] == f"{library_section_id}:{special_keys[1]}" + assert len(result["children"]) == len( + mock_plex_server.library.sectionByID(library_section_id).recentlyAdded() + ) # Browse into a Plex TV show library msg_id += 1 @@ -103,7 +155,7 @@ async def test_browse_media(hass, hass_ws_client): result_id = result[ATTR_MEDIA_CONTENT_ID] assert len(result["children"]) == len( mock_plex_server.library.sectionByID(result_id).all() - ) + ) + len(SPECIAL_METHODS) # Browse into a Plex TV show msg_id += 1 @@ -112,8 +164,8 @@ async def test_browse_media(hass, hass_ws_client): "id": msg_id, "type": "media_player/browse_media", "entity_id": media_players[0], - ATTR_MEDIA_CONTENT_TYPE: result["children"][0][ATTR_MEDIA_CONTENT_TYPE], - ATTR_MEDIA_CONTENT_ID: str(result["children"][0][ATTR_MEDIA_CONTENT_ID]), + ATTR_MEDIA_CONTENT_TYPE: result["children"][-1][ATTR_MEDIA_CONTENT_TYPE], + ATTR_MEDIA_CONTENT_ID: str(result["children"][-1][ATTR_MEDIA_CONTENT_ID]), } )